From ceca56129dab83bcd0b8bc8570f4199ca81236a9 Mon Sep 17 00:00:00 2001 From: Vasile Chindris Date: Mon, 8 Apr 2024 21:50:33 +0300 Subject: [PATCH 001/134] feat(SLB-315): count in-text links references to media entities --- .../cms/config/sync/entity_usage.settings.yml | 6 +- .../sync/linkit.linkit_profile.gutenberg.yml | 2 +- packages/drupal/custom/custom.module | 71 +++++++++++++++++++ .../25086be7-ca5f-4ff8-9695-b9c71a676d4e.yml | 6 +- tests/e2e/specs/drupal/entity-usage.spec.ts | 16 +++++ 5 files changed, 96 insertions(+), 5 deletions(-) create mode 100644 tests/e2e/specs/drupal/entity-usage.spec.ts diff --git a/apps/cms/config/sync/entity_usage.settings.yml b/apps/cms/config/sync/entity_usage.settings.yml index 575153e65..8f69040e1 100644 --- a/apps/cms/config/sync/entity_usage.settings.yml +++ b/apps/cms/config/sync/entity_usage.settings.yml @@ -5,8 +5,8 @@ local_task_enabled_entity_types: - node track_enabled_source_entity_types: - node - - content_moderation_state - block_content + - content_moderation_state - menu_link_content - media - redirect @@ -14,8 +14,8 @@ track_enabled_source_entity_types: - path_alias track_enabled_target_entity_types: - node - - content_moderation_state - block_content + - content_moderation_state - menu_link_content - media - redirect @@ -32,6 +32,8 @@ track_enabled_plugins: - html_link - ckeditor_image - block_field + - gutenberg_linked_content + - gutenberg_referenced_content - gutenberg_media_embed track_enabled_base_fields: false site_domains: { } diff --git a/apps/cms/config/sync/linkit.linkit_profile.gutenberg.yml b/apps/cms/config/sync/linkit.linkit_profile.gutenberg.yml index 844075fdc..4eded13a4 100644 --- a/apps/cms/config/sync/linkit.linkit_profile.gutenberg.yml +++ b/apps/cms/config/sync/linkit.linkit_profile.gutenberg.yml @@ -28,6 +28,6 @@ matchers: bundles: document: document group_by_bundle: 0 - substitution_type: null + substitution_type: canonical limit: '100' weight: -9 diff --git a/packages/drupal/custom/custom.module b/packages/drupal/custom/custom.module index 030c5f087..07ac7bc44 100644 --- a/packages/drupal/custom/custom.module +++ b/packages/drupal/custom/custom.module @@ -6,6 +6,8 @@ use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Language\LanguageInterface; use Drupal\Core\Render\Element; use Drupal\Core\Render\RenderContext; +use Drupal\Core\StreamWrapper\LocalStream; +use Drupal\Core\StreamWrapper\StreamWrapperInterface; use Drupal\Core\Utility\Error as ErrorUtil; use Drupal\file\Entity\File; use Drupal\media\Entity\Media; @@ -90,6 +92,75 @@ function custom_silverback_gutenberg_link_processor_outbound_url_alter( } } +/** + * Implements hook_silverback_gutenberg_link_processor_inbound_link_alter(). + * @param \DOMElement $link + * @param \Drupal\silverback_gutenberg\LinkProcessor $linkProcessor + * + * @return void + * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException + * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException + */ +function custom_silverback_gutenberg_link_processor_inbound_link_alter( + \DOMElement $link, + LinkProcessor $linkProcessor +) { + // For inbound links (when data gets saved), if the link points to a public + // file, then we want to replace the href value with "media/uuid/edit" and + // also make sure the data-id and data-entity-type attributes have the proper + // values (the uuid and the 'media' value). This is a special case for media, + // because the url of the media item is replaced by + // custom_silverback_gutenberg_link_processor_outbound_url_alter() with the + // file path, so now on inbound we need to basically do the opposite. This is + // needed so that the entity usage integration works properly (where the + // data-id and data-entity-type attributes are checked). + $href = $link->getAttribute('href'); + /* @var \Drupal\Core\StreamWrapper\StreamWrapperManager $wrapperManager */ + $wrapperManager = \Drupal::service('stream_wrapper_manager'); + /* @var \Drupal\Core\StreamWrapper\StreamWrapperInterface[] $visibleWrappers */ + $visibleWrappers = $wrapperManager->getWrappers(StreamWrapperInterface::VISIBLE); + foreach ($visibleWrappers as $scheme => $wrapperInfo) { + $wrapper = $wrapperManager->getViaScheme($scheme); + // We are only handle local streams for now. + if (!$wrapper instanceof LocalStream) { + continue; + } + if (!str_starts_with($href, '/' . $wrapper->getDirectoryPath() . '/')) { + continue; + } + // When searching for a file inside the database, the wrapper uri is used + // instead of the directory path. That is why we need the wrapper in the + // first place. + $fileuri = str_replace('/' . $wrapper->getDirectoryPath() . '/', $wrapper->getUri(), urldecode($href)); + $files = \Drupal::entityTypeManager() + ->getStorage('file') + ->loadByProperties(['uri' => $fileuri]); + // No files found, just continue to the next wrapper. + if (empty($files)) { + continue; + } + $file = array_shift($files); + $usageList = \Drupal::service('file.usage')->listUsage($file); + // If the media file usage list is empty, then this is probably some kind of + // orphan file, or tracked by some other entity type. + if (empty($usageList['file']['media'])) { + continue; + } + $mids = array_keys($usageList['file']['media']); + $mid = reset($mids); + $media = Media::load($mid); + if (empty($media)) { + continue; + } + // If we got here, we found a matching media item, so we can populate the + // link metadata with its values and just break out of the wrappers loop. + $link->setAttribute('href', '/media/' . $media->uuid() . '/edit'); + $link->setAttribute('data-id', $media->uuid()); + $link->setAttribute('data-entity-type', 'media'); + break; + } +} + /** * Implements hook_form_alter(). * diff --git a/packages/drupal/test_content/content/node/25086be7-ca5f-4ff8-9695-b9c71a676d4e.yml b/packages/drupal/test_content/content/node/25086be7-ca5f-4ff8-9695-b9c71a676d4e.yml index e519e6fe0..0cceba025 100644 --- a/packages/drupal/test_content/content/node/25086be7-ca5f-4ff8-9695-b9c71a676d4e.yml +++ b/packages/drupal/test_content/content/node/25086be7-ca5f-4ff8-9695-b9c71a676d4e.yml @@ -4,6 +4,8 @@ _meta: uuid: 25086be7-ca5f-4ff8-9695-b9c71a676d4e bundle: page default_langcode: en + depends: + b998ae5e-8b56-40ca-8f80-fd4404e7e716: media default: revision_uid: - @@ -47,11 +49,11 @@ default: -

link to file

+

link to file

-

link to page

+

link to page

format: gutenberg diff --git a/tests/e2e/specs/drupal/entity-usage.spec.ts b/tests/e2e/specs/drupal/entity-usage.spec.ts new file mode 100644 index 000000000..118ac4421 --- /dev/null +++ b/tests/e2e/specs/drupal/entity-usage.spec.ts @@ -0,0 +1,16 @@ +import { expect, test } from '@playwright/test'; + +import { cmsUrl } from '../../helpers/url'; + +test.describe('entity-usage', () => { + test.use({ storageState: '.auth/admin.json' }); + + test('media usage works with inline doc links', async ({ page }) => { + await page.goto(cmsUrl('/admin/content/media')); + page.locator('div.view-content').getByText('Example DOCX document').click(); + page.locator('a.tabs__link').getByText('Usage').click(); + await expect( + page.locator('table').getByText('Page with links'), + ).toBeVisible(); + }); +}); From 21e0147c16ca098e5bacde54fde51239e055ee9c Mon Sep 17 00:00:00 2001 From: Vasile Chindris Date: Tue, 9 Apr 2024 09:07:16 +0300 Subject: [PATCH 002/134] test(SLB-315): update schema tests --- tests/schema/specs/links.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/schema/specs/links.spec.ts b/tests/schema/specs/links.spec.ts index 44c93b623..2262cb2b1 100644 --- a/tests/schema/specs/links.spec.ts +++ b/tests/schema/specs/links.spec.ts @@ -29,9 +29,9 @@ test('Links', async () => { { "__typename": "BlockMarkup", "markup": " -

link to file

+

link to file

-

link to page

+

link to page

", }, ], From 987919d1ae11e708cfbcaab9900b86f9ee025e0b Mon Sep 17 00:00:00 2001 From: Alex Tkachev Date: Wed, 17 Apr 2024 16:58:29 +0400 Subject: [PATCH 003/134] chore(SLB-289): disable color and typography options on the list block --- packages/drupal/gutenberg_blocks/src/customisations.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/drupal/gutenberg_blocks/src/customisations.ts b/packages/drupal/gutenberg_blocks/src/customisations.ts index 70d97ccac..fb0355c8e 100644 --- a/packages/drupal/gutenberg_blocks/src/customisations.ts +++ b/packages/drupal/gutenberg_blocks/src/customisations.ts @@ -16,6 +16,10 @@ drupalSettings.gutenberg._listeners.init.push( const paragraphBlock = wp.blocks.getBlockType('core/paragraph'); paragraphBlock.supports.typography.fontSize = false; paragraphBlock.supports.typography.dropCap = false; + // @ts-ignore + const listBlock = wp.blocks.getBlockType('core/list'); + listBlock.supports.color = false; + listBlock.supports.typography = false; }, // Allow common blocks to be placed only in the Content block. From 163b1835233abec5ccb09c44bbe1bf7e7ce9cd03 Mon Sep 17 00:00:00 2001 From: Alex Tkachev Date: Wed, 17 Apr 2024 16:58:57 +0400 Subject: [PATCH 004/134] feat(SLB-289): extend the core/list block with a new style option --- .../gutenberg_blocks/src/filters/list.tsx | 105 ++++++++++++++++++ packages/drupal/gutenberg_blocks/src/index.ts | 1 + 2 files changed, 106 insertions(+) create mode 100644 packages/drupal/gutenberg_blocks/src/filters/list.tsx diff --git a/packages/drupal/gutenberg_blocks/src/filters/list.tsx b/packages/drupal/gutenberg_blocks/src/filters/list.tsx new file mode 100644 index 000000000..707e7aa61 --- /dev/null +++ b/packages/drupal/gutenberg_blocks/src/filters/list.tsx @@ -0,0 +1,105 @@ +import clsx from 'clsx'; +import { InspectorControls } from 'wordpress__block-editor'; +import { PanelBody, SelectControl } from 'wordpress__components'; +import { createHigherOrderComponent } from 'wordpress__compose'; +import { addFilter } from 'wordpress__hooks'; + +// @ts-ignore +const __ = Drupal.t; + +addFilter( + 'blocks.registerBlockType', + 'custom/list', + (settings: { name: string; attributes: any }) => { + if (settings.name === 'core/list') { + settings.attributes = Object.assign(settings.attributes, { + customListStyle: { + type: 'string', + default: '', + }, + }); + } + return settings; + }, +); + +addFilter( + 'editor.BlockEdit', + 'custom/list', + createHigherOrderComponent( + // eslint-disable-next-line react/display-name + (BlockEdit) => (props) => { + const { name, attributes, setAttributes, isSelected } = props; + const { customListStyle, ordered } = attributes; + if (!customListStyle === undefined) { + setAttributes({ customListStyle: '' }); + } + return ( + <> + + {isSelected && name === 'core/list' && !ordered ? ( + + + { + setAttributes({ + customListStyle, + }); + }} + /> + + + ) : null} + + ); + }, + 'withCustomListStyleControls', + ), +); + +addFilter( + 'editor.BlockListBlock', + 'custom/list', + createHigherOrderComponent( + // eslint-disable-next-line react/display-name + (BlockListBlock) => (props) => { + const { name, attributes } = props; + if (name === 'core/list') { + const { customListStyle, ordered } = attributes; + if (!ordered && customListStyle) { + props.className = getCustomListClass( + props.className, + customListStyle, + ); + } + } + return ; + }, + 'withCustomListStyleBlockClass', + ), +); + +addFilter( + 'blocks.getSaveContent.extraProps', + 'custom/list', + (props: any, blockType: { name: string }, attributes: any) => { + if (blockType.name === 'core/list') { + const { customListStyle, ordered } = attributes; + if (!ordered && customListStyle) { + props.className = getCustomListClass(props.className, customListStyle); + } + } + return props; + }, +); + +function getCustomListClass(existingClassName: string, style: string) { + return clsx(existingClassName, 'list-style--' + style); +} diff --git a/packages/drupal/gutenberg_blocks/src/index.ts b/packages/drupal/gutenberg_blocks/src/index.ts index a0aff90af..74af52921 100644 --- a/packages/drupal/gutenberg_blocks/src/index.ts +++ b/packages/drupal/gutenberg_blocks/src/index.ts @@ -5,4 +5,5 @@ import './blocks/heading'; import './blocks/form'; import './blocks/image-teaser'; import './blocks/image-teasers'; +import './filters/list'; import './blocks/cta'; From d409a189ff8abd108dd028741d1830652a746f7f Mon Sep 17 00:00:00 2001 From: Alex Tkachev Date: Wed, 17 Apr 2024 16:59:14 +0400 Subject: [PATCH 005/134] feat(SLB-289): introduce custom/image-with-text block --- packages/drupal/gutenberg_blocks/package.json | 1 + .../src/blocks/image-with-text.tsx | 79 ++++++ packages/drupal/gutenberg_blocks/src/index.ts | 1 + .../a397ca48-8fad-411e-8901-0eba2feb989c.yml | 26 ++ .../ceb9b2a7-4c4c-4084-ada9-d5f6505d466b.yml | 6 + packages/schema/src/fragments/Page.gql | 1 + .../PageContent/BlockImageWithText.gql | 9 + packages/schema/src/schema.graphql | 6 + .../src/components/Organisms/PageDisplay.tsx | 15 ++ pnpm-lock.yaml | 254 +++++++++++++++--- tests/schema/specs/blocks.spec.ts | 41 +++ 11 files changed, 402 insertions(+), 37 deletions(-) create mode 100644 packages/drupal/gutenberg_blocks/src/blocks/image-with-text.tsx create mode 100644 packages/schema/src/fragments/PageContent/BlockImageWithText.gql diff --git a/packages/drupal/gutenberg_blocks/package.json b/packages/drupal/gutenberg_blocks/package.json index 093ca1970..b31ec99c1 100644 --- a/packages/drupal/gutenberg_blocks/package.json +++ b/packages/drupal/gutenberg_blocks/package.json @@ -27,6 +27,7 @@ "@types/wordpress__core-data": "2.4.5", "@types/wordpress__data": "6.0.2", "@types/wordpress__editor": "13.0.0", + "@types/wordpress__hooks": "2.4.0", "@vitejs/plugin-react": "4.2.1", "typescript": "^5.3.3", "vite": "^5.0.10" diff --git a/packages/drupal/gutenberg_blocks/src/blocks/image-with-text.tsx b/packages/drupal/gutenberg_blocks/src/blocks/image-with-text.tsx new file mode 100644 index 000000000..d4717d4aa --- /dev/null +++ b/packages/drupal/gutenberg_blocks/src/blocks/image-with-text.tsx @@ -0,0 +1,79 @@ +import { InnerBlocks, InspectorControls } from 'wordpress__block-editor'; +import { registerBlockType } from 'wordpress__blocks'; +import { PanelBody, SelectControl } from 'wordpress__components'; +import { dispatch } from 'wordpress__data'; + +import { DrupalMediaEntity } from '../utils/drupal-media'; + +// @ts-ignore +const { t: __ } = Drupal; + +registerBlockType('custom/image-with-text', { + title: __('Image with Text'), + icon: 'cover-image', + category: 'layout', + attributes: { + mediaEntityIds: { + type: 'array', + }, + imagePosition: { + type: 'string', + default: 'left', + }, + }, + edit: (props) => { + const { setAttributes } = props; + return ( + <> + + + { + setAttributes({ + imagePosition, + }); + }} + /> + + +
+
{__('Image with Text')}
+ { + // @ts-ignore + error = typeof error === 'string' ? error : error[2]; + dispatch('core/notices').createWarningNotice(error); + }} + /> + +
+ + ); + }, + save: () => , +}); diff --git a/packages/drupal/gutenberg_blocks/src/index.ts b/packages/drupal/gutenberg_blocks/src/index.ts index 74af52921..6eae568bf 100644 --- a/packages/drupal/gutenberg_blocks/src/index.ts +++ b/packages/drupal/gutenberg_blocks/src/index.ts @@ -5,5 +5,6 @@ import './blocks/heading'; import './blocks/form'; import './blocks/image-teaser'; import './blocks/image-teasers'; +import './blocks/image-with-text'; import './filters/list'; import './blocks/cta'; diff --git a/packages/drupal/test_content/content/node/a397ca48-8fad-411e-8901-0eba2feb989c.yml b/packages/drupal/test_content/content/node/a397ca48-8fad-411e-8901-0eba2feb989c.yml index de316ba27..43e5fe62d 100644 --- a/packages/drupal/test_content/content/node/a397ca48-8fad-411e-8901-0eba2feb989c.yml +++ b/packages/drupal/test_content/content/node/a397ca48-8fad-411e-8901-0eba2feb989c.yml @@ -60,6 +60,32 @@ default: + + +

All kinds of allowed blocks

+ + + +
  • bla
+ + + +

Heading

+ + + +
12
34
Caption
+ + + +

Quote

Citation
+ + + +

+ + +

Starting from this paragraph, all the following blocks should be aggregated, as they are just HTML

diff --git a/packages/drupal/test_content/content/node/ceb9b2a7-4c4c-4084-ada9-d5f6505d466b.yml b/packages/drupal/test_content/content/node/ceb9b2a7-4c4c-4084-ada9-d5f6505d466b.yml index 6c19a491d..53ade0b64 100644 --- a/packages/drupal/test_content/content/node/ceb9b2a7-4c4c-4084-ada9-d5f6505d466b.yml +++ b/packages/drupal/test_content/content/node/ceb9b2a7-4c4c-4084-ada9-d5f6505d466b.yml @@ -67,6 +67,12 @@ default: + + + +

+ + format: gutenberg summary: '' diff --git a/packages/schema/src/fragments/Page.gql b/packages/schema/src/fragments/Page.gql index 38c057340..0a78d9dea 100644 --- a/packages/schema/src/fragments/Page.gql +++ b/packages/schema/src/fragments/Page.gql @@ -33,6 +33,7 @@ fragment Page on Page { ...BlockForm ...BlockImageTeasers ...BlockCta + ...BlockImageWithText } metaTags { tag diff --git a/packages/schema/src/fragments/PageContent/BlockImageWithText.gql b/packages/schema/src/fragments/PageContent/BlockImageWithText.gql new file mode 100644 index 000000000..b026e8676 --- /dev/null +++ b/packages/schema/src/fragments/PageContent/BlockImageWithText.gql @@ -0,0 +1,9 @@ +fragment BlockImageWithText on BlockImageWithText { + image { + source(width: 1536, sizes: [[768, 768], [1536, 1536]]) + alt + } + textContent { + markup + } +} diff --git a/packages/schema/src/schema.graphql b/packages/schema/src/schema.graphql index 9300972cb..3d86b6850 100644 --- a/packages/schema/src/schema.graphql +++ b/packages/schema/src/schema.graphql @@ -199,6 +199,7 @@ union PageContent @resolveEditorBlockType = | BlockForm | BlockImageTeasers | BlockCta + | BlockImageWithText type BlockForm @type(id: "custom/form") { url: Url @resolveEditorBlockAttribute(key: "formId") @webformIdToUrl(id: "$") @@ -244,6 +245,11 @@ type BlockCta @type(id: "custom/cta") { openInNewTab: Boolean @resolveEditorBlockAttribute(key: "openInNewTab") } +type BlockImageWithText @type(id: "custom/image-with-text") { + image: MediaImage @resolveEditorBlockMedia + textContent: BlockMarkup @resolveEditorBlockChildren @seek(pos: 0) +} + input PaginationInput { limit: Int! offset: Int! diff --git a/packages/ui/src/components/Organisms/PageDisplay.tsx b/packages/ui/src/components/Organisms/PageDisplay.tsx index bcefcb2ec..975d447c0 100644 --- a/packages/ui/src/components/Organisms/PageDisplay.tsx +++ b/packages/ui/src/components/Organisms/PageDisplay.tsx @@ -43,6 +43,21 @@ export function PageDisplay(page: PageFragment) { ); case 'BlockCta': return ; + case 'BlockImageWithText': + return ( + // TODO: Implement BlockImageWithText +
+ BlockImageWithText goes here +
+ ); default: throw new UnreachableCaseError(block); } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8aca358f2..d516514ba 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -354,6 +354,9 @@ importers: '@types/wordpress__editor': specifier: 13.0.0 version: 13.0.0(react-dom@18.2.0)(react@18.2.0) + '@types/wordpress__hooks': + specifier: 2.4.0 + version: 2.4.0 '@vitejs/plugin-react': specifier: 4.2.1 version: 4.2.1(vite@5.0.10) @@ -2993,6 +2996,7 @@ packages: cpu: [ppc64] os: [aix] requiresBuild: true + dev: true optional: true /@esbuild/aix-ppc64@0.20.0: @@ -3036,6 +3040,7 @@ packages: cpu: [arm64] os: [android] requiresBuild: true + dev: true optional: true /@esbuild/android-arm64@0.20.0: @@ -3079,6 +3084,7 @@ packages: cpu: [arm] os: [android] requiresBuild: true + dev: true optional: true /@esbuild/android-arm@0.20.0: @@ -3122,6 +3128,7 @@ packages: cpu: [x64] os: [android] requiresBuild: true + dev: true optional: true /@esbuild/android-x64@0.20.0: @@ -3165,6 +3172,7 @@ packages: cpu: [arm64] os: [darwin] requiresBuild: true + dev: true optional: true /@esbuild/darwin-arm64@0.20.0: @@ -3208,6 +3216,7 @@ packages: cpu: [x64] os: [darwin] requiresBuild: true + dev: true optional: true /@esbuild/darwin-x64@0.20.0: @@ -3251,6 +3260,7 @@ packages: cpu: [arm64] os: [freebsd] requiresBuild: true + dev: true optional: true /@esbuild/freebsd-arm64@0.20.0: @@ -3294,6 +3304,7 @@ packages: cpu: [x64] os: [freebsd] requiresBuild: true + dev: true optional: true /@esbuild/freebsd-x64@0.20.0: @@ -3337,6 +3348,7 @@ packages: cpu: [arm64] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/linux-arm64@0.20.0: @@ -3380,6 +3392,7 @@ packages: cpu: [arm] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/linux-arm@0.20.0: @@ -3423,6 +3436,7 @@ packages: cpu: [ia32] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/linux-ia32@0.20.0: @@ -3466,6 +3480,7 @@ packages: cpu: [loong64] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/linux-loong64@0.20.0: @@ -3509,6 +3524,7 @@ packages: cpu: [mips64el] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/linux-mips64el@0.20.0: @@ -3552,6 +3568,7 @@ packages: cpu: [ppc64] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/linux-ppc64@0.20.0: @@ -3595,6 +3612,7 @@ packages: cpu: [riscv64] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/linux-riscv64@0.20.0: @@ -3638,6 +3656,7 @@ packages: cpu: [s390x] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/linux-s390x@0.20.0: @@ -3681,6 +3700,7 @@ packages: cpu: [x64] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/linux-x64@0.20.0: @@ -3724,6 +3744,7 @@ packages: cpu: [x64] os: [netbsd] requiresBuild: true + dev: true optional: true /@esbuild/netbsd-x64@0.20.0: @@ -3767,6 +3788,7 @@ packages: cpu: [x64] os: [openbsd] requiresBuild: true + dev: true optional: true /@esbuild/openbsd-x64@0.20.0: @@ -3810,6 +3832,7 @@ packages: cpu: [x64] os: [sunos] requiresBuild: true + dev: true optional: true /@esbuild/sunos-x64@0.20.0: @@ -3853,6 +3876,7 @@ packages: cpu: [arm64] os: [win32] requiresBuild: true + dev: true optional: true /@esbuild/win32-arm64@0.20.0: @@ -3896,6 +3920,7 @@ packages: cpu: [ia32] os: [win32] requiresBuild: true + dev: true optional: true /@esbuild/win32-ia32@0.20.0: @@ -3939,6 +3964,7 @@ packages: cpu: [x64] os: [win32] requiresBuild: true + dev: true optional: true /@esbuild/win32-x64@0.20.0: @@ -7147,7 +7173,7 @@ packages: react-refresh: 0.14.0 schema-utils: 3.3.0 source-map: 0.7.4 - webpack: 5.91.0(esbuild@0.19.12) + webpack: 5.91.0 /@pnpm/config.env-replace@1.1.0: resolution: {integrity: sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==} @@ -9956,6 +9982,10 @@ packages: - react-dom dev: true + /@types/wordpress__hooks@2.4.0: + resolution: {integrity: sha512-SixjDsBvPynmMQxBFX3i4nuWtLifqtml7PhSosoCk5RJy9S7xs/G6vvtcnAuslV3BJTyvxEbAsz4WpKaF0bqMw==} + dev: true + /@types/wordpress__keycodes@2.18.0: resolution: {integrity: sha512-09ku81E6tjB//bI5PatKM/rhTJ0aEmNLvhIn380orea+SX6/09t7luO27zxI0uHSs/1Pxue1ifrMKY+gE5lfzw==} deprecated: This is a stub types definition. @wordpress/keycodes provides its own type definitions, so you do not need this installed. @@ -10082,6 +10112,7 @@ packages: typescript: 5.3.3 transitivePeerDependencies: - supports-color + dev: true /@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0)(eslint@7.32.0)(typescript@5.4.4): resolution: {integrity: sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==} @@ -10110,7 +10141,6 @@ packages: transitivePeerDependencies: - supports-color dev: false - optional: true /@typescript-eslint/eslint-plugin@6.17.0(@typescript-eslint/parser@6.17.0)(eslint@7.0.0)(typescript@5.3.3): resolution: {integrity: sha512-Vih/4xLXmY7V490dGwBQJTpIZxH4ZFH6eCVmQ4RFkB+wmaCTDAx4dtgoWwMNGKLkqRY1L6rPqzEbjorRnDo4rQ==} @@ -10226,8 +10256,6 @@ packages: typescript: 5.4.4 transitivePeerDependencies: - supports-color - dev: false - optional: true /@typescript-eslint/parser@6.17.0(eslint@7.0.0)(typescript@5.3.3): resolution: {integrity: sha512-C4bBaX2orvhK+LlwrY8oWGmSl4WolCfYm513gEccdWZj0CwGadbIADb0FtVEcI+WzUyjyoBj2JRP8g25E6IB8A==} @@ -10269,6 +10297,7 @@ packages: typescript: 5.3.3 transitivePeerDependencies: - supports-color + dev: true /@typescript-eslint/scope-manager@5.62.0: resolution: {integrity: sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==} @@ -10283,6 +10312,7 @@ packages: dependencies: '@typescript-eslint/types': 6.17.0 '@typescript-eslint/visitor-keys': 6.17.0 + dev: true /@typescript-eslint/scope-manager@6.21.0: resolution: {integrity: sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==} @@ -10329,6 +10359,7 @@ packages: typescript: 5.3.3 transitivePeerDependencies: - supports-color + dev: true /@typescript-eslint/type-utils@5.62.0(eslint@7.32.0)(typescript@5.4.4): resolution: {integrity: sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==} @@ -10349,7 +10380,6 @@ packages: transitivePeerDependencies: - supports-color dev: false - optional: true /@typescript-eslint/type-utils@6.17.0(eslint@7.0.0)(typescript@5.3.3): resolution: {integrity: sha512-hDXcWmnbtn4P2B37ka3nil3yi3VCQO2QEB9gBiHJmQp5wmyQWqnjA85+ZcE8c4FqnaB6lBwMrPkgd4aBYz3iNg==} @@ -10398,6 +10428,7 @@ packages: /@typescript-eslint/types@6.17.0: resolution: {integrity: sha512-qRKs9tvc3a4RBcL/9PXtKSehI/q8wuU9xYJxe97WFxnzH8NWWtcW3ffNS+EWg8uPvIerhjsEZ+rHtDqOCiH57A==} engines: {node: ^16.0.0 || >=18.0.0} + dev: true /@typescript-eslint/types@6.21.0: resolution: {integrity: sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==} @@ -10484,7 +10515,6 @@ packages: typescript: 5.4.4 transitivePeerDependencies: - supports-color - dev: false /@typescript-eslint/typescript-estree@6.17.0(typescript@5.3.3): resolution: {integrity: sha512-gVQe+SLdNPfjlJn5VNGhlOhrXz4cajwFd5kAgWtZ9dCZf4XJf8xmgCTLIqec7aha3JwgLI2CK6GY1043FRxZwg==} @@ -10506,6 +10536,7 @@ packages: typescript: 5.3.3 transitivePeerDependencies: - supports-color + dev: true /@typescript-eslint/typescript-estree@6.21.0(typescript@5.3.3): resolution: {integrity: sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==} @@ -10566,6 +10597,7 @@ packages: transitivePeerDependencies: - supports-color - typescript + dev: true /@typescript-eslint/utils@5.62.0(eslint@7.32.0)(typescript@5.4.4): resolution: {integrity: sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==} @@ -10586,7 +10618,6 @@ packages: - supports-color - typescript dev: false - optional: true /@typescript-eslint/utils@6.17.0(eslint@7.0.0)(typescript@5.3.3): resolution: {integrity: sha512-LofsSPjN/ITNkzV47hxas2JCsNCEnGhVvocfyOcLzT9c/tSZE7SfhS/iWtzP1lKNOEfLhRTZz6xqI8N2RzweSQ==} @@ -10658,6 +10689,7 @@ packages: dependencies: '@typescript-eslint/types': 6.17.0 eslint-visitor-keys: 3.4.3 + dev: true /@typescript-eslint/visitor-keys@6.21.0: resolution: {integrity: sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==} @@ -12524,7 +12556,7 @@ packages: loader-utils: 2.0.4 make-dir: 3.1.0 schema-utils: 2.7.1 - webpack: 5.91.0(esbuild@0.19.12) + webpack: 5.91.0 /babel-plugin-add-module-exports@1.0.4: resolution: {integrity: sha512-g+8yxHUZ60RcyaUpfNzy56OtWW+x9cyEe9j+CranqLiqbju2yf/Cy6ZtYK40EZxtrdHllzlVZgLmcOUCTlJ7Jg==} @@ -12630,7 +12662,7 @@ packages: '@babel/core': 7.24.4 '@babel/runtime': 7.24.4 '@babel/types': 7.24.0 - gatsby: 5.13.3(babel-eslint@10.1.0)(esbuild@0.19.12)(react-dom@18.2.0)(react@18.2.0)(typescript@5.3.3) + gatsby: 5.13.3(babel-eslint@10.1.0)(react-dom@18.2.0)(react@18.2.0)(typescript@5.4.4) gatsby-core-utils: 4.13.1 dev: false @@ -14417,7 +14449,7 @@ packages: postcss-value-parser: 4.2.0 schema-utils: 3.3.0 semver: 7.6.0 - webpack: 5.91.0(esbuild@0.19.12) + webpack: 5.91.0 /css-minimizer-webpack-plugin@2.0.0(webpack@5.91.0): resolution: {integrity: sha512-cG/uc94727tx5pBNtb1Sd7gvUPzwmcQi1lkpfqTpdkuNq75hJCw7bIVsCNijLm4dhDcr1atvuysl2rZqOG8Txw==} @@ -14439,7 +14471,7 @@ packages: schema-utils: 3.3.0 serialize-javascript: 5.0.1 source-map: 0.6.1 - webpack: 5.91.0(esbuild@0.19.12) + webpack: 5.91.0 /css-select@4.3.0: resolution: {integrity: sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==} @@ -16570,6 +16602,7 @@ packages: '@esbuild/win32-arm64': 0.19.12 '@esbuild/win32-ia32': 0.19.12 '@esbuild/win32-x64': 0.19.12 + dev: true /esbuild@0.20.0: resolution: {integrity: sha512-6iwE3Y2RVYCME1jLpBqq7LQWK3MW6vjV2bZy6gt/WrqkY+WE74Spyc0ThAOYpMtITvnjX09CrC6ym7A/m9mebA==} @@ -16762,6 +16795,44 @@ packages: eslint-plugin-react: 7.34.1(eslint@7.32.0) eslint-plugin-react-hooks: 4.6.0(eslint@7.32.0) typescript: 5.3.3 + dev: true + + /eslint-config-react-app@6.0.0(@typescript-eslint/eslint-plugin@5.62.0)(@typescript-eslint/parser@5.62.0)(babel-eslint@10.1.0)(eslint-plugin-flowtype@5.10.0)(eslint-plugin-import@2.29.1)(eslint-plugin-jsx-a11y@6.8.0)(eslint-plugin-react-hooks@4.6.0)(eslint-plugin-react@7.34.1)(eslint@7.32.0)(typescript@5.4.4): + resolution: {integrity: sha512-bpoAAC+YRfzq0dsTk+6v9aHm/uqnDwayNAXleMypGl6CpxI9oXXscVHo4fk3eJPIn+rsbtNetB4r/ZIidFIE8A==} + engines: {node: ^10.12.0 || >=12.0.0} + peerDependencies: + '@typescript-eslint/eslint-plugin': ^4.0.0 + '@typescript-eslint/parser': ^4.0.0 + babel-eslint: ^10.0.0 + eslint: ^7.5.0 + eslint-plugin-flowtype: ^5.2.0 + eslint-plugin-import: ^2.22.0 + eslint-plugin-jest: ^24.0.0 + eslint-plugin-jsx-a11y: ^6.3.1 + eslint-plugin-react: ^7.20.3 + eslint-plugin-react-hooks: ^4.0.8 + eslint-plugin-testing-library: ^3.9.0 + typescript: '*' + peerDependenciesMeta: + eslint-plugin-jest: + optional: true + eslint-plugin-testing-library: + optional: true + typescript: + optional: true + dependencies: + '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0)(eslint@7.32.0)(typescript@5.4.4) + '@typescript-eslint/parser': 5.62.0(eslint@7.32.0)(typescript@5.4.4) + babel-eslint: 10.1.0(eslint@7.32.0) + confusing-browser-globals: 1.0.11 + eslint: 7.32.0 + eslint-plugin-flowtype: 5.10.0(eslint@7.32.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0)(eslint@7.32.0) + eslint-plugin-jsx-a11y: 6.8.0(eslint@7.32.0) + eslint-plugin-react: 7.34.1(eslint@7.32.0) + eslint-plugin-react-hooks: 4.6.0(eslint@7.32.0) + typescript: 5.4.4 + dev: false /eslint-import-resolver-node@0.3.9: resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} @@ -16772,6 +16843,34 @@ packages: transitivePeerDependencies: - supports-color + /eslint-module-utils@2.8.1(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-node@0.3.9)(eslint@7.32.0): + resolution: {integrity: sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: '*' + eslint-import-resolver-node: '*' + eslint-import-resolver-typescript: '*' + eslint-import-resolver-webpack: '*' + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + eslint: + optional: true + eslint-import-resolver-node: + optional: true + eslint-import-resolver-typescript: + optional: true + eslint-import-resolver-webpack: + optional: true + dependencies: + '@typescript-eslint/parser': 5.62.0(eslint@7.32.0)(typescript@5.4.4) + debug: 3.2.7 + eslint: 7.32.0 + eslint-import-resolver-node: 0.3.9 + transitivePeerDependencies: + - supports-color + /eslint-module-utils@2.8.1(@typescript-eslint/parser@6.17.0)(eslint-import-resolver-node@0.3.9)(eslint@7.0.0): resolution: {integrity: sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==} engines: {node: '>=4'} @@ -16828,6 +16927,7 @@ packages: eslint-import-resolver-node: 0.3.9 transitivePeerDependencies: - supports-color + dev: true /eslint-plugin-flowtype@5.10.0(eslint@7.32.0): resolution: {integrity: sha512-vcz32f+7TP+kvTUyMXZmCnNujBQZDNmcqPImw8b9PZ+16w1Qdm6ryRuYZYVaG9xRqqmAPr2Cs9FAX5gN+x/bjw==} @@ -16871,7 +16971,7 @@ packages: '@typescript-eslint/parser': optional: true dependencies: - '@typescript-eslint/parser': 5.62.0(eslint@7.32.0)(typescript@5.3.3) + '@typescript-eslint/parser': 5.62.0(eslint@7.32.0)(typescript@5.4.4) array-includes: 3.1.8 array.prototype.findlastindex: 1.2.5 array.prototype.flat: 1.3.2 @@ -16880,7 +16980,7 @@ packages: doctrine: 2.1.0 eslint: 7.32.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.17.0)(eslint-import-resolver-node@0.3.9)(eslint@7.32.0) + eslint-module-utils: 2.8.1(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-node@0.3.9)(eslint@7.32.0) hasown: 2.0.2 is-core-module: 2.13.1 is-glob: 4.0.3 @@ -16963,6 +17063,7 @@ packages: - eslint-import-resolver-typescript - eslint-import-resolver-webpack - supports-color + dev: true /eslint-plugin-jsx-a11y@6.8.0(eslint@7.32.0): resolution: {integrity: sha512-Hdh937BS3KdwwbBaKd5+PLCOmYY6U4f2h9Z2ktwtNKvIdIEu137rjYbcb9ApSbVJfWxANNuiKTD/9tOKjK9qOA==} @@ -17175,7 +17276,7 @@ packages: micromatch: 4.0.5 normalize-path: 3.0.0 schema-utils: 3.3.0 - webpack: 5.91.0(esbuild@0.19.12) + webpack: 5.91.0 /eslint@7.0.0: resolution: {integrity: sha512-qY1cwdOxMONHJfGqw52UOpZDeqXy8xmD0u8CT6jIstil72jkhURC704W8CFyTPDPllz4z4lu0Ql1+07PG/XdIg==} @@ -17979,7 +18080,7 @@ packages: dependencies: loader-utils: 2.0.4 schema-utils: 3.3.0 - webpack: 5.91.0(esbuild@0.19.12) + webpack: 5.91.0 /file-system-cache@2.3.0: resolution: {integrity: sha512-l4DMNdsIPsVnKrgEXbJwDJsA5mB8rGwHYERMgqQx/xAUtChPJMre1bXBzDEqqVbWv9AIbFezXMxeEkZDSrXUOQ==} @@ -18349,6 +18450,39 @@ packages: tapable: 1.1.3 typescript: 5.3.3 webpack: 5.91.0(esbuild@0.19.12) + dev: true + + /fork-ts-checker-webpack-plugin@6.5.3(eslint@7.32.0)(typescript@5.4.4)(webpack@5.91.0): + resolution: {integrity: sha512-SbH/l9ikmMWycd5puHJKTkZJKddF4iRLyW3DeZ08HTI7NGyLS38MXd/KGgeWumQO7YNQbW2u/NtPT2YowbPaGQ==} + engines: {node: '>=10', yarn: '>=1.0.0'} + peerDependencies: + eslint: '>= 6' + typescript: '>= 2.7' + vue-template-compiler: '*' + webpack: '>= 4' + peerDependenciesMeta: + eslint: + optional: true + vue-template-compiler: + optional: true + dependencies: + '@babel/code-frame': 7.24.2 + '@types/json-schema': 7.0.15 + chalk: 4.1.2 + chokidar: 3.6.0 + cosmiconfig: 6.0.0 + deepmerge: 4.3.1 + eslint: 7.32.0 + fs-extra: 9.1.0 + glob: 7.2.3 + memfs: 3.5.3 + minimatch: 3.1.2 + schema-utils: 2.7.0 + semver: 7.6.0 + tapable: 1.1.3 + typescript: 5.4.4 + webpack: 5.91.0 + dev: false /form-data-encoder@2.1.4: resolution: {integrity: sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==} @@ -18623,7 +18757,7 @@ packages: dependencies: '@types/node-fetch': 2.6.11 fs-extra: 9.1.0 - gatsby: 5.13.3(babel-eslint@10.1.0)(esbuild@0.19.12)(react-dom@18.2.0)(react@18.2.0)(typescript@5.3.3) + gatsby: 5.13.3(babel-eslint@10.1.0)(react-dom@18.2.0)(react@18.2.0)(typescript@5.4.4) lodash: 4.17.21 node-fetch: 2.7.0 p-queue: 6.6.2 @@ -18785,7 +18919,7 @@ packages: chokidar: 3.6.0 fs-exists-cached: 1.0.0 fs-extra: 11.2.0 - gatsby: 5.13.3(babel-eslint@10.1.0)(esbuild@0.19.12)(react-dom@18.2.0)(react@18.2.0)(typescript@5.3.3) + gatsby: 5.13.3(babel-eslint@10.1.0)(react-dom@18.2.0)(react@18.2.0)(typescript@5.4.4) gatsby-core-utils: 4.13.1 gatsby-page-utils: 3.13.1 gatsby-plugin-utils: 4.13.1(gatsby@5.13.3)(graphql@16.8.1) @@ -18854,7 +18988,7 @@ packages: debug: 4.3.4 filenamify: 4.3.0 fs-extra: 11.2.0 - gatsby: 5.13.3(babel-eslint@10.1.0)(esbuild@0.19.12)(react-dom@18.2.0)(react@18.2.0)(typescript@5.3.3) + gatsby: 5.13.3(babel-eslint@10.1.0)(react-dom@18.2.0)(react@18.2.0)(typescript@5.4.4) gatsby-core-utils: 4.13.1 gatsby-plugin-utils: 4.13.1(gatsby@5.13.3)(graphql@16.8.1) lodash: 4.17.21 @@ -18913,7 +19047,7 @@ packages: '@babel/preset-typescript': 7.24.1(@babel/core@7.24.4) '@babel/runtime': 7.24.4 babel-plugin-remove-graphql-queries: 5.13.1(@babel/core@7.24.4)(gatsby@5.13.3) - gatsby: 5.13.3(babel-eslint@10.1.0)(esbuild@0.19.12)(react-dom@18.2.0)(react@18.2.0)(typescript@5.3.3) + gatsby: 5.13.3(babel-eslint@10.1.0)(react-dom@18.2.0)(react@18.2.0)(typescript@5.4.4) transitivePeerDependencies: - supports-color dev: false @@ -18951,7 +19085,7 @@ packages: '@babel/runtime': 7.24.4 fastq: 1.17.1 fs-extra: 11.2.0 - gatsby: 5.13.3(babel-eslint@10.1.0)(esbuild@0.19.12)(react-dom@18.2.0)(react@18.2.0)(typescript@5.3.3) + gatsby: 5.13.3(babel-eslint@10.1.0)(react-dom@18.2.0)(react@18.2.0)(typescript@5.4.4) gatsby-core-utils: 4.13.1 gatsby-sharp: 1.13.0 graphql: 16.8.1 @@ -19453,7 +19587,7 @@ packages: - webpack-hot-middleware - webpack-plugin-serve - /gatsby@5.13.3(babel-eslint@10.1.0)(esbuild@0.19.12)(react-dom@18.2.0)(react@18.2.0)(typescript@5.3.3): + /gatsby@5.13.3(babel-eslint@10.1.0)(react-dom@18.2.0)(react@18.2.0)(typescript@5.4.4): resolution: {integrity: sha512-SSnGpjswK20BQORcvTbtK8eI+W4QUG+u8rdVswB4suva6BfvTakW2wiktj7E2MdO4NjRvlgJjF5dUUncU5nldA==} engines: {node: '>=18.0.0'} hasBin: true @@ -19487,8 +19621,8 @@ packages: '@pmmmwh/react-refresh-webpack-plugin': 0.5.11(react-refresh@0.14.0)(webpack@5.91.0) '@sigmacomputing/babel-plugin-lodash': 3.3.5 '@types/http-proxy': 1.17.14 - '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0)(eslint@7.32.0)(typescript@5.3.3) - '@typescript-eslint/parser': 5.62.0(eslint@7.32.0)(typescript@5.3.3) + '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0)(eslint@7.32.0)(typescript@5.4.4) + '@typescript-eslint/parser': 5.62.0(eslint@7.32.0)(typescript@5.4.4) '@vercel/webpack-asset-relocator-loader': 1.7.3 acorn-loose: 8.4.0 acorn-walk: 8.3.2 @@ -19526,9 +19660,9 @@ packages: enhanced-resolve: 5.16.0 error-stack-parser: 2.1.4 eslint: 7.32.0 - eslint-config-react-app: 6.0.0(@typescript-eslint/eslint-plugin@5.62.0)(@typescript-eslint/parser@5.62.0)(babel-eslint@10.1.0)(eslint-plugin-flowtype@5.10.0)(eslint-plugin-import@2.29.1)(eslint-plugin-jsx-a11y@6.8.0)(eslint-plugin-react-hooks@4.6.0)(eslint-plugin-react@7.34.1)(eslint@7.32.0)(typescript@5.3.3) + eslint-config-react-app: 6.0.0(@typescript-eslint/eslint-plugin@5.62.0)(@typescript-eslint/parser@5.62.0)(babel-eslint@10.1.0)(eslint-plugin-flowtype@5.10.0)(eslint-plugin-import@2.29.1)(eslint-plugin-jsx-a11y@6.8.0)(eslint-plugin-react-hooks@4.6.0)(eslint-plugin-react@7.34.1)(eslint@7.32.0)(typescript@5.4.4) eslint-plugin-flowtype: 5.10.0(eslint@7.32.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.17.0)(eslint@7.32.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0)(eslint@7.32.0) eslint-plugin-jsx-a11y: 6.8.0(eslint@7.32.0) eslint-plugin-react: 7.34.1(eslint@7.32.0) eslint-plugin-react-hooks: 4.6.0(eslint@7.32.0) @@ -19600,7 +19734,7 @@ packages: query-string: 6.14.1 raw-loader: 4.0.2(webpack@5.91.0) react: 18.2.0 - react-dev-utils: 12.0.1(eslint@7.32.0)(typescript@5.3.3)(webpack@5.91.0) + react-dev-utils: 12.0.1(eslint@7.32.0)(typescript@5.4.4)(webpack@5.91.0) react-dom: 18.2.0(react@18.2.0) react-refresh: 0.14.0 react-server-dom-webpack: 0.0.0-experimental-c8b778b7f-20220825(react@18.2.0)(webpack@5.91.0) @@ -19618,13 +19752,13 @@ packages: strip-ansi: 6.0.1 style-loader: 2.0.0(webpack@5.91.0) style-to-object: 0.4.4 - terser-webpack-plugin: 5.3.10(esbuild@0.19.12)(webpack@5.91.0) + terser-webpack-plugin: 5.3.10(webpack@5.91.0) tmp: 0.2.3 true-case-path: 2.2.1 type-of: 2.0.1 url-loader: 4.1.1(file-loader@6.2.0)(webpack@5.91.0) uuid: 8.3.2 - webpack: 5.91.0(esbuild@0.19.12) + webpack: 5.91.0 webpack-dev-middleware: 4.3.0(webpack@5.91.0) webpack-merge: 5.10.0 webpack-stats-plugin: 1.1.3 @@ -24269,7 +24403,7 @@ packages: dependencies: loader-utils: 2.0.4 schema-utils: 3.3.0 - webpack: 5.91.0(esbuild@0.19.12) + webpack: 5.91.0 webpack-sources: 1.4.3 /mini-svg-data-uri@1.4.4: @@ -24299,6 +24433,7 @@ packages: engines: {node: '>=16 || 14 >=14.17'} dependencies: brace-expansion: 2.0.1 + dev: true /minimatch@9.0.4: resolution: {integrity: sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==} @@ -25121,7 +25256,7 @@ packages: dependencies: loader-utils: 2.0.4 schema-utils: 3.3.0 - webpack: 5.91.0(esbuild@0.19.12) + webpack: 5.91.0 /nullthrows@1.1.1: resolution: {integrity: sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==} @@ -26376,7 +26511,7 @@ packages: klona: 2.0.6 postcss: 8.4.38 semver: 7.6.0 - webpack: 5.91.0(esbuild@0.19.12) + webpack: 5.91.0 /postcss-merge-longhand@5.1.7(postcss@8.4.38): resolution: {integrity: sha512-YCI9gZB+PLNskrK0BB3/2OzPnGhPkBEwmwhfYk1ilBHYVAZB7/tkTHFBAnCrvBBOmeYyMYw3DMjT55SyxMBzjQ==} @@ -27371,7 +27506,7 @@ packages: dependencies: loader-utils: 2.0.4 schema-utils: 3.3.0 - webpack: 5.91.0(esbuild@0.19.12) + webpack: 5.91.0 /rbush@3.0.1: resolution: {integrity: sha512-XRaVO0YecOpEuIvbhbpTrZgoiI6xBlz6hnlr6EHhd+0x9ase6EmeN+hdwwUaJvLcsFFQ8iWVF1GAK1yB0BWi0w==} @@ -27537,6 +27672,49 @@ packages: - eslint - supports-color - vue-template-compiler + dev: true + + /react-dev-utils@12.0.1(eslint@7.32.0)(typescript@5.4.4)(webpack@5.91.0): + resolution: {integrity: sha512-84Ivxmr17KjUupyqzFode6xKhjwuEJDROWKJy/BthkL7Wn6NJ8h4WE6k/exAv6ImS+0oZLRRW5j/aINMHyeGeQ==} + engines: {node: '>=14'} + peerDependencies: + typescript: '>=2.7' + webpack: '>=4' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@babel/code-frame': 7.24.2 + address: 1.2.2 + browserslist: 4.23.0 + chalk: 4.1.2 + cross-spawn: 7.0.3 + detect-port-alt: 1.1.6 + escape-string-regexp: 4.0.0 + filesize: 8.0.7 + find-up: 5.0.0 + fork-ts-checker-webpack-plugin: 6.5.3(eslint@7.32.0)(typescript@5.4.4)(webpack@5.91.0) + global-modules: 2.0.0 + globby: 11.1.0 + gzip-size: 6.0.0 + immer: 9.0.21 + is-root: 2.1.0 + loader-utils: 3.2.1 + open: 8.4.2 + pkg-up: 3.1.0 + prompts: 2.4.2 + react-error-overlay: 6.0.11 + recursive-readdir: 2.2.3 + shell-quote: 1.8.1 + strip-ansi: 6.0.1 + text-table: 0.2.0 + typescript: 5.4.4 + webpack: 5.91.0 + transitivePeerDependencies: + - eslint + - supports-color + - vue-template-compiler + dev: false /react-dnd-html5-backend@14.1.0: resolution: {integrity: sha512-6ONeqEC3XKVf4eVmMTe0oPds+c5B9Foyj8p/ZKLb7kL2qh9COYxiBHv3szd6gztqi/efkmriywLUVlPotqoJyw==} @@ -27936,7 +28114,7 @@ packages: loose-envify: 1.4.0 neo-async: 2.6.2 react: 18.2.0 - webpack: 5.91.0(esbuild@0.19.12) + webpack: 5.91.0 /react-split-pane@0.1.92(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-GfXP1xSzLMcLJI5BM36Vh7GgZBpy+U/X0no+VM3fxayv+p1Jly5HpMofZJraeaMl73b3hvlr+N9zJKvLB/uz9w==} @@ -30290,7 +30468,7 @@ packages: dependencies: loader-utils: 2.0.4 schema-utils: 3.3.0 - webpack: 5.91.0(esbuild@0.19.12) + webpack: 5.91.0 /style-to-object@0.3.0: resolution: {integrity: sha512-CzFnRRXhzWIdItT3OmF8SQfWyahHhjq3HwcMNCNLn+N7klOOqPjMeG/4JSu77D7ypZdGvSzvkrbyeTMizz2VrA==} @@ -30682,6 +30860,7 @@ packages: serialize-javascript: 6.0.2 terser: 5.30.3 webpack: 5.91.0(esbuild@0.19.12) + dev: true /terser-webpack-plugin@5.3.10(webpack@5.91.0): resolution: {integrity: sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==} @@ -31026,6 +31205,7 @@ packages: typescript: '>=4.2.0' dependencies: typescript: 5.3.3 + dev: true /ts-dedent@2.2.0: resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==} @@ -31174,7 +31354,6 @@ packages: dependencies: tslib: 1.14.1 typescript: 5.4.4 - dev: false /tunnel-agent@0.6.0: resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} @@ -31846,7 +32025,7 @@ packages: loader-utils: 2.0.4 mime-types: 2.1.35 schema-utils: 3.3.0 - webpack: 5.91.0(esbuild@0.19.12) + webpack: 5.91.0 /url@0.11.3: resolution: {integrity: sha512-6hxOLGfZASQK/cijlZnZJTq8OXAkt/3YGfQX45vvMYXpZoo8NdWZcY73K108Jf759lS1Bv/8wXnHDTSz17dSRw==} @@ -32817,7 +32996,7 @@ packages: mime-types: 2.1.35 range-parser: 1.2.1 schema-utils: 3.3.0 - webpack: 5.91.0(esbuild@0.19.12) + webpack: 5.91.0 /webpack-merge@5.10.0: resolution: {integrity: sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==} @@ -32964,6 +33143,7 @@ packages: - '@swc/core' - esbuild - uglify-js + dev: true /website-scraper@5.3.1: resolution: {integrity: sha512-gogqPXD2gVsxoyd2yRiympw3rA5GuEpD1CaDEJ/J8zzanx7hkbTtneoO1SGs436PpLbWVcUge+6APGLhzsuZPA==} diff --git a/tests/schema/specs/blocks.spec.ts b/tests/schema/specs/blocks.spec.ts index 9a246e3f6..4b86529d4 100644 --- a/tests/schema/specs/blocks.spec.ts +++ b/tests/schema/specs/blocks.spec.ts @@ -53,6 +53,15 @@ test('Blocks', async () => { text openInNewTab } + ... on BlockImageWithText { + image { + __typename + } + textContent { + __typename + markup + } + } } } { @@ -106,6 +115,28 @@ test('Blocks', async () => { "__typename": "BlockForm", "url": "http://127.0.0.1:8000/en/form/contact", }, + { + "__typename": "BlockImageWithText", + "image": { + "__typename": "MediaImage", + }, + "textContent": { + "__typename": "BlockMarkup", + "markup": " +

All kinds of allowed blocks

+ +
  • bla
+ +

Heading

+ +
12
34
Caption
+ +

Quote

Citation
+ +

+ ", + }, + }, { "__typename": "BlockMarkup", "markup": " @@ -209,6 +240,16 @@ test('Blocks', async () => { "text": null, "url": null, }, + { + "__typename": "BlockImageWithText", + "image": null, + "textContent": { + "__typename": "BlockMarkup", + "markup": " +

+ ", + }, + }, ], "hero": { "__typename": "Hero", From 73191bf6d44d53fce29c31b4efaaa5ab26a1638b Mon Sep 17 00:00:00 2001 From: Vasile Chindris Date: Tue, 16 Apr 2024 17:13:15 +0300 Subject: [PATCH 006/134] feat(SLB-297 SLB-296): replace the core quote block with a custom one --- apps/cms/config/sync/gutenberg.settings.yml | 6 +- packages/drupal/gutenberg_blocks/css/edit.css | 5 + .../GutenbergValidator/QuoteValidator.php | 40 +++++++ .../gutenberg_blocks/src/blocks/quote.tsx | 100 ++++++++++++++++++ packages/drupal/gutenberg_blocks/src/index.ts | 1 + 5 files changed, 151 insertions(+), 1 deletion(-) create mode 100644 packages/drupal/gutenberg_blocks/src/Plugin/Validation/GutenbergValidator/QuoteValidator.php create mode 100644 packages/drupal/gutenberg_blocks/src/blocks/quote.tsx diff --git a/apps/cms/config/sync/gutenberg.settings.yml b/apps/cms/config/sync/gutenberg.settings.yml index e58422162..ed9ef9d7d 100644 --- a/apps/cms/config/sync/gutenberg.settings.yml +++ b/apps/cms/config/sync/gutenberg.settings.yml @@ -4,12 +4,12 @@ page_template_lock: all page_allowed_blocks: core/paragraph: core/paragraph core/list: core/list - core/quote: core/quote core/table: core/table core/all: 0 core/image: 0 core/heading: 0 core/gallery: 0 + core/quote: 0 core/audio: 0 core/button: 0 core/buttons: 0 @@ -75,6 +75,7 @@ page_allowed_drupal_blocks: drupalblock/all_core: 0 page_title_block: 0 drupalblock/all_forms: 0 + masquerade: 0 user_login_block: 0 drupalblock/all_lists_views_: 0 'views_block:content_recent-block_1': 0 @@ -91,3 +92,6 @@ page_allowed_drupal_blocks: system_branding_block: 0 node_syndicate_block: 0 drupalblock/all_user: 0 + drupalblock/all_webform: 0 + webform_block: 0 + webform_submission_limit_block: 0 diff --git a/packages/drupal/gutenberg_blocks/css/edit.css b/packages/drupal/gutenberg_blocks/css/edit.css index f4a49c277..1c603451e 100644 --- a/packages/drupal/gutenberg_blocks/css/edit.css +++ b/packages/drupal/gutenberg_blocks/css/edit.css @@ -12,6 +12,11 @@ content: ''; } +.gutenberg__editor blockquote .quote-author, +.gutenberg__editor blockquote .quote-role { + text-align: right; +} + .gutenberg__editor .container-wrapper { display: block; position: relative; diff --git a/packages/drupal/gutenberg_blocks/src/Plugin/Validation/GutenbergValidator/QuoteValidator.php b/packages/drupal/gutenberg_blocks/src/Plugin/Validation/GutenbergValidator/QuoteValidator.php new file mode 100644 index 000000000..1f9f9d9c9 --- /dev/null +++ b/packages/drupal/gutenberg_blocks/src/Plugin/Validation/GutenbergValidator/QuoteValidator.php @@ -0,0 +1,40 @@ + [ + 'field_label' => $this->t('Quote text'), + 'rules' => ['required'], + ], + 'author' => [ + 'field_label' => $this->t('Quote author'), + 'rules' => ['required'], + ], + ]; + } + +} diff --git a/packages/drupal/gutenberg_blocks/src/blocks/quote.tsx b/packages/drupal/gutenberg_blocks/src/blocks/quote.tsx new file mode 100644 index 000000000..1dacdf98b --- /dev/null +++ b/packages/drupal/gutenberg_blocks/src/blocks/quote.tsx @@ -0,0 +1,100 @@ +import { RichText } from 'wordpress__block-editor'; +import { registerBlockType } from 'wordpress__blocks'; +import { compose, withState } from 'wordpress__compose'; +import { dispatch } from 'wordpress__data'; + +import { cleanUpText } from '../utils/clean-up-text'; +import { DrupalMediaEntity } from '../utils/drupal-media'; + +declare const Drupal: { t: (s: string) => string }; + +const { t: __ } = Drupal; + +// @ts-ignore +const { setPlainTextAttribute } = silverbackGutenbergUtils; + +// @ts-ignore +registerBlockType(`custom/quote`, { + title: __('Quote'), + icon: 'format-quote', + category: 'text', + attributes: { + text: { + type: 'string', + }, + author: { + tpye: 'string', + }, + role: { + type: 'string', + }, + mediaEntityIds: { + type: 'array', + }, + }, + // @ts-ignore + edit: compose(withState())((props) => { + return ( +
+ { + props.setAttributes({ + text: cleanUpText(text), + }); + }} + /> + { + setPlainTextAttribute(props, 'author', author); + }} + /> + { + setPlainTextAttribute(props, 'role', role); + }} + /> + { + // @ts-ignore + error = typeof error === 'string' ? error : error[2]; + dispatch('core/notices').createWarningNotice(error); + }} + /> +
+ ); + }), + save() { + return null; + }, +}); diff --git a/packages/drupal/gutenberg_blocks/src/index.ts b/packages/drupal/gutenberg_blocks/src/index.ts index a0aff90af..332b5720f 100644 --- a/packages/drupal/gutenberg_blocks/src/index.ts +++ b/packages/drupal/gutenberg_blocks/src/index.ts @@ -6,3 +6,4 @@ import './blocks/form'; import './blocks/image-teaser'; import './blocks/image-teasers'; import './blocks/cta'; +import './blocks/quote'; From b2ed4e06664203290282efe072cd0430bf6aae73 Mon Sep 17 00:00:00 2001 From: Vasile Chindris Date: Tue, 16 Apr 2024 17:28:27 +0300 Subject: [PATCH 007/134] feat(SLB-297 SLB-296): quote graphql block type --- packages/drupal/gutenberg_blocks/src/blocks/quote.tsx | 2 +- packages/schema/src/schema.graphql | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/drupal/gutenberg_blocks/src/blocks/quote.tsx b/packages/drupal/gutenberg_blocks/src/blocks/quote.tsx index 1dacdf98b..f42c3f82d 100644 --- a/packages/drupal/gutenberg_blocks/src/blocks/quote.tsx +++ b/packages/drupal/gutenberg_blocks/src/blocks/quote.tsx @@ -46,7 +46,7 @@ registerBlockType(`custom/quote`, { keepPlaceholderOnFocus={false} onChange={(text) => { props.setAttributes({ - text: cleanUpText(text), + text: cleanUpText(text, ['strong']), }); }} /> diff --git a/packages/schema/src/schema.graphql b/packages/schema/src/schema.graphql index 9300972cb..e379509fb 100644 --- a/packages/schema/src/schema.graphql +++ b/packages/schema/src/schema.graphql @@ -199,6 +199,7 @@ union PageContent @resolveEditorBlockType = | BlockForm | BlockImageTeasers | BlockCta + | BlockQuote type BlockForm @type(id: "custom/form") { url: Url @resolveEditorBlockAttribute(key: "formId") @webformIdToUrl(id: "$") @@ -244,6 +245,13 @@ type BlockCta @type(id: "custom/cta") { openInNewTab: Boolean @resolveEditorBlockAttribute(key: "openInNewTab") } +type BlockQuote @type(id: "custom/quote") { + text: Markup @resolveEditorBlockAttribute(key: "text") + author: String @resolveEditorBlockAttribute(key: "author") + role: String @resolveEditorBlockAttribute(key: "role") + image: MediaImage @resolveEditorBlockMedia +} + input PaginationInput { limit: Int! offset: Int! From ee9cd6d2598e79ee9e89eb1506ae7340f9afef3a Mon Sep 17 00:00:00 2001 From: Vasile Chindris Date: Tue, 16 Apr 2024 17:42:21 +0300 Subject: [PATCH 008/134] feat(SLB-296): organism placeholder for quote blocks --- packages/schema/src/fragments/Page.gql | 1 + packages/schema/src/fragments/PageContent/BlockQuote.gql | 9 +++++++++ .../src/components/Organisms/PageContent/BlockQuote.tsx | 7 +++++++ packages/ui/src/components/Organisms/PageDisplay.tsx | 3 +++ 4 files changed, 20 insertions(+) create mode 100644 packages/schema/src/fragments/PageContent/BlockQuote.gql create mode 100644 packages/ui/src/components/Organisms/PageContent/BlockQuote.tsx diff --git a/packages/schema/src/fragments/Page.gql b/packages/schema/src/fragments/Page.gql index 38c057340..4c3fe1e11 100644 --- a/packages/schema/src/fragments/Page.gql +++ b/packages/schema/src/fragments/Page.gql @@ -33,6 +33,7 @@ fragment Page on Page { ...BlockForm ...BlockImageTeasers ...BlockCta + ...BlockQuote } metaTags { tag diff --git a/packages/schema/src/fragments/PageContent/BlockQuote.gql b/packages/schema/src/fragments/PageContent/BlockQuote.gql new file mode 100644 index 000000000..b50478e0a --- /dev/null +++ b/packages/schema/src/fragments/PageContent/BlockQuote.gql @@ -0,0 +1,9 @@ +fragment BlockQuote on BlockQuote { + text + author + role + image { + source(width: 1536, sizes: [[768, 768], [1536, 1536]]) + alt + } +} diff --git a/packages/ui/src/components/Organisms/PageContent/BlockQuote.tsx b/packages/ui/src/components/Organisms/PageContent/BlockQuote.tsx new file mode 100644 index 000000000..4676f6302 --- /dev/null +++ b/packages/ui/src/components/Organisms/PageContent/BlockQuote.tsx @@ -0,0 +1,7 @@ +import { BlockQuoteFragment } from '@custom/schema'; +import React from 'react'; + +export function BlockQuote(props: BlockQuoteFragment) { + console.log('Block quote props:', props); + return <>; +} diff --git a/packages/ui/src/components/Organisms/PageDisplay.tsx b/packages/ui/src/components/Organisms/PageDisplay.tsx index bcefcb2ec..21af1c5eb 100644 --- a/packages/ui/src/components/Organisms/PageDisplay.tsx +++ b/packages/ui/src/components/Organisms/PageDisplay.tsx @@ -8,6 +8,7 @@ import { BlockCta } from './PageContent/BlockCta'; import { BlockForm } from './PageContent/BlockForm'; import { BlockMarkup } from './PageContent/BlockMarkup'; import { BlockMedia } from './PageContent/BlockMedia'; +import { BlockQuote } from './PageContent/BlockQuote'; import { PageHero } from './PageHero'; export function PageDisplay(page: PageFragment) { @@ -43,6 +44,8 @@ export function PageDisplay(page: PageFragment) { ); case 'BlockCta': return ; + case 'BlockQuote': + return
; default: throw new UnreachableCaseError(block); } From 74732a5080aa8ccfa6674867e6bc4bd9e14a9b47 Mon Sep 17 00:00:00 2001 From: Vasile Chindris Date: Tue, 16 Apr 2024 20:26:22 +0300 Subject: [PATCH 009/134] chore(SLB-297 SLB-296): update the quote text from text to quote --- .../Validation/GutenbergValidator/QuoteValidator.php | 2 +- packages/drupal/gutenberg_blocks/src/blocks/quote.tsx | 10 +++++----- .../schema/src/fragments/PageContent/BlockQuote.gql | 2 +- packages/schema/src/schema.graphql | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/drupal/gutenberg_blocks/src/Plugin/Validation/GutenbergValidator/QuoteValidator.php b/packages/drupal/gutenberg_blocks/src/Plugin/Validation/GutenbergValidator/QuoteValidator.php index 1f9f9d9c9..53df53041 100644 --- a/packages/drupal/gutenberg_blocks/src/Plugin/Validation/GutenbergValidator/QuoteValidator.php +++ b/packages/drupal/gutenberg_blocks/src/Plugin/Validation/GutenbergValidator/QuoteValidator.php @@ -26,7 +26,7 @@ public function applies(array $block): bool { */ public function validatedFields($block = []): array { return [ - 'text' => [ + 'quote' => [ 'field_label' => $this->t('Quote text'), 'rules' => ['required'], ], diff --git a/packages/drupal/gutenberg_blocks/src/blocks/quote.tsx b/packages/drupal/gutenberg_blocks/src/blocks/quote.tsx index f42c3f82d..1aea31b6a 100644 --- a/packages/drupal/gutenberg_blocks/src/blocks/quote.tsx +++ b/packages/drupal/gutenberg_blocks/src/blocks/quote.tsx @@ -19,7 +19,7 @@ registerBlockType(`custom/quote`, { icon: 'format-quote', category: 'text', attributes: { - text: { + quote: { type: 'string', }, author: { @@ -37,16 +37,16 @@ registerBlockType(`custom/quote`, { return (
{ + onChange={(quote) => { props.setAttributes({ - text: cleanUpText(text, ['strong']), + quote: cleanUpText(quote, ['strong']), }); }} /> diff --git a/packages/schema/src/fragments/PageContent/BlockQuote.gql b/packages/schema/src/fragments/PageContent/BlockQuote.gql index b50478e0a..785efb620 100644 --- a/packages/schema/src/fragments/PageContent/BlockQuote.gql +++ b/packages/schema/src/fragments/PageContent/BlockQuote.gql @@ -1,5 +1,5 @@ fragment BlockQuote on BlockQuote { - text + quote author role image { diff --git a/packages/schema/src/schema.graphql b/packages/schema/src/schema.graphql index e379509fb..43bff39d9 100644 --- a/packages/schema/src/schema.graphql +++ b/packages/schema/src/schema.graphql @@ -246,7 +246,7 @@ type BlockCta @type(id: "custom/cta") { } type BlockQuote @type(id: "custom/quote") { - text: Markup @resolveEditorBlockAttribute(key: "text") + quote: Markup @resolveEditorBlockAttribute(key: "quote") author: String @resolveEditorBlockAttribute(key: "author") role: String @resolveEditorBlockAttribute(key: "role") image: MediaImage @resolveEditorBlockMedia From a8da4bfb5eb265010f69a8efe50fb4066670f7df Mon Sep 17 00:00:00 2001 From: Vasile Chindris Date: Tue, 16 Apr 2024 20:44:05 +0300 Subject: [PATCH 010/134] test(SLB-297): update default content and schema tests to include quote blocks --- .../a397ca48-8fad-411e-8901-0eba2feb989c.yml | 5 ++++ .../ceb9b2a7-4c4c-4084-ada9-d5f6505d466b.yml | 2 ++ tests/schema/specs/blocks.spec.ts | 24 +++++++++++++++++++ 3 files changed, 31 insertions(+) diff --git a/packages/drupal/test_content/content/node/a397ca48-8fad-411e-8901-0eba2feb989c.yml b/packages/drupal/test_content/content/node/a397ca48-8fad-411e-8901-0eba2feb989c.yml index de316ba27..0ee37d6aa 100644 --- a/packages/drupal/test_content/content/node/a397ca48-8fad-411e-8901-0eba2feb989c.yml +++ b/packages/drupal/test_content/content/node/a397ca48-8fad-411e-8901-0eba2feb989c.yml @@ -91,6 +91,9 @@ default: + + +

@@ -172,6 +175,8 @@ translations: + + format: gutenberg summary: '' diff --git a/packages/drupal/test_content/content/node/ceb9b2a7-4c4c-4084-ada9-d5f6505d466b.yml b/packages/drupal/test_content/content/node/ceb9b2a7-4c4c-4084-ada9-d5f6505d466b.yml index 6c19a491d..704d39565 100644 --- a/packages/drupal/test_content/content/node/ceb9b2a7-4c4c-4084-ada9-d5f6505d466b.yml +++ b/packages/drupal/test_content/content/node/ceb9b2a7-4c4c-4084-ada9-d5f6505d466b.yml @@ -67,6 +67,8 @@ default: + + format: gutenberg summary: '' diff --git a/tests/schema/specs/blocks.spec.ts b/tests/schema/specs/blocks.spec.ts index 9a246e3f6..45eaa2e14 100644 --- a/tests/schema/specs/blocks.spec.ts +++ b/tests/schema/specs/blocks.spec.ts @@ -53,6 +53,14 @@ test('Blocks', async () => { text openInNewTab } + ... on BlockQuote { + quote + author + role + image { + __typename + } + } } } { @@ -161,6 +169,15 @@ test('Blocks', async () => { "text": "CTA with link to media", "url": "/media/[numeric]", }, + { + "__typename": "BlockQuote", + "author": "John Doe", + "image": { + "__typename": "MediaImage", + }, + "quote": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus sagittis nisi nec neque porta, a ornare ligula efficitur.", + "role": "Project manager", + }, { "__typename": "BlockMarkup", "markup": " @@ -209,6 +226,13 @@ test('Blocks', async () => { "text": null, "url": null, }, + { + "__typename": "BlockQuote", + "author": "Jane Doe", + "image": null, + "quote": "In vitae diam quis odio tincidunt faucibus eget ut libero", + "role": null, + }, ], "hero": { "__typename": "Hero", From d5a4548f0f756f215d6353a2db5848e325f13d27 Mon Sep 17 00:00:00 2001 From: Luqmaan Essop Date: Tue, 16 Apr 2024 14:51:14 +0000 Subject: [PATCH 011/134] style: adjust quote styling --- .../Organisms/PageContent/BlockMarkup.tsx | 19 +++++++++++++++++++ packages/ui/src/tailwind.css | 6 ++++++ 2 files changed, 25 insertions(+) diff --git a/packages/ui/src/components/Organisms/PageContent/BlockMarkup.tsx b/packages/ui/src/components/Organisms/PageContent/BlockMarkup.tsx index 54f29a9fb..443b8fd19 100644 --- a/packages/ui/src/components/Organisms/PageContent/BlockMarkup.tsx +++ b/packages/ui/src/components/Organisms/PageContent/BlockMarkup.tsx @@ -49,6 +49,25 @@ export function BlockMarkup(props: BlockMarkupFragment) { ); }, + blockquote: ({ children }: PropsWithChildren<{}>) => { + return ( +
+ + + + {children} +
+ ); + }, }} markup={props.markup} /> diff --git a/packages/ui/src/tailwind.css b/packages/ui/src/tailwind.css index f9b68485a..39d6591e2 100644 --- a/packages/ui/src/tailwind.css +++ b/packages/ui/src/tailwind.css @@ -4,3 +4,9 @@ @tailwind base; @tailwind components; @tailwind utilities; + +/* Prose overrides */ +.lg\:prose-xl + :where(blockquote):not(:where([class~='not-prose'], [class~='not-prose'] *)) { + padding-left: 0 !important; +} From 297db9f0561ee9dc6e8b73bfaffac253dac013f0 Mon Sep 17 00:00:00 2001 From: Luqmaan Essop Date: Wed, 17 Apr 2024 09:02:17 +0000 Subject: [PATCH 012/134] style: add block quote styling --- .../Organisms/PageContent/BlockMarkup.tsx | 1 - .../PageContent/BlockQuote.stories.ts | 23 ++++++++++ .../Organisms/PageContent/BlockQuote.tsx | 46 +++++++++++++++++-- packages/ui/src/tailwind.css | 14 ++++++ 4 files changed, 80 insertions(+), 4 deletions(-) create mode 100644 packages/ui/src/components/Organisms/PageContent/BlockQuote.stories.ts diff --git a/packages/ui/src/components/Organisms/PageContent/BlockMarkup.tsx b/packages/ui/src/components/Organisms/PageContent/BlockMarkup.tsx index 443b8fd19..a83f81b79 100644 --- a/packages/ui/src/components/Organisms/PageContent/BlockMarkup.tsx +++ b/packages/ui/src/components/Organisms/PageContent/BlockMarkup.tsx @@ -18,7 +18,6 @@ export function BlockMarkup(props: BlockMarkupFragment) { className={clsx([ 'mx-auto max-w-3xl prose lg:prose-xl mt-10', 'prose-a:text-indigo-600', - 'prose-blockquote:border-indigo-200', 'prose-em:text-indigo-600', 'prose-strong:text-indigo-600', 'marker:text-indigo-600 marker:font-bold', diff --git a/packages/ui/src/components/Organisms/PageContent/BlockQuote.stories.ts b/packages/ui/src/components/Organisms/PageContent/BlockQuote.stories.ts new file mode 100644 index 000000000..e5efd0c53 --- /dev/null +++ b/packages/ui/src/components/Organisms/PageContent/BlockQuote.stories.ts @@ -0,0 +1,23 @@ +import { Markup } from '@custom/schema'; +import Portrait from '@stories/portrait.jpg?as=metadata'; +import { Meta, StoryObj } from '@storybook/react'; + +import { image } from '../../../helpers/image'; +import { BlockQuote } from './BlockQuote'; + +export default { + component: BlockQuote, +} satisfies Meta; + +export const Quote = { + args: { + role: 'test role', + author: 'Author name', + image: { + source: image({ src: Portrait.src, width: 50, height: 50 }), + alt: 'Portrait', + }, + quote: + '

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

' as Markup, + }, +} satisfies StoryObj; diff --git a/packages/ui/src/components/Organisms/PageContent/BlockQuote.tsx b/packages/ui/src/components/Organisms/PageContent/BlockQuote.tsx index 4676f6302..adab18a20 100644 --- a/packages/ui/src/components/Organisms/PageContent/BlockQuote.tsx +++ b/packages/ui/src/components/Organisms/PageContent/BlockQuote.tsx @@ -1,7 +1,47 @@ -import { BlockQuoteFragment } from '@custom/schema'; +import { BlockQuoteFragment, Html, Image } from '@custom/schema'; import React from 'react'; export function BlockQuote(props: BlockQuoteFragment) { - console.log('Block quote props:', props); - return <>; + return ( +
+
+ + Quote Symbol + + + {props.quote && } +
+ {props.image && ( + {props.image.alt + )} +
+ {props.author &&

{props.author}

} +
+ {props.role && ( +

+ / + + {props.role} + +

+ )} +
+
+
+ ); } diff --git a/packages/ui/src/tailwind.css b/packages/ui/src/tailwind.css index 39d6591e2..f6eb52ad0 100644 --- a/packages/ui/src/tailwind.css +++ b/packages/ui/src/tailwind.css @@ -10,3 +10,17 @@ :where(blockquote):not(:where([class~='not-prose'], [class~='not-prose'] *)) { padding-left: 0 !important; } + +.prose + :where(blockquote p:first-of-type):not( + :where([class~='not-prose'], [class~='not-prose'] *) + )::before { + content: '' !important; +} + +.prose + :where(blockquote p:first-of-type):not( + :where([class~='not-prose'], [class~='not-prose'] *) + )::after { + content: '' !important; +} From 1fa8aea38bdcbd59e39eb703e2467b784b04a6ad Mon Sep 17 00:00:00 2001 From: Luqmaan Essop Date: Thu, 18 Apr 2024 05:02:12 +0000 Subject: [PATCH 013/134] chore: adjust image for quote uthor --- .../Organisms/PageContent/BlockQuote.stories.ts | 4 ++-- packages/ui/static/stories/avatar.jpg | Bin 0 -> 6782 bytes 2 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 packages/ui/static/stories/avatar.jpg diff --git a/packages/ui/src/components/Organisms/PageContent/BlockQuote.stories.ts b/packages/ui/src/components/Organisms/PageContent/BlockQuote.stories.ts index e5efd0c53..ffe6090f1 100644 --- a/packages/ui/src/components/Organisms/PageContent/BlockQuote.stories.ts +++ b/packages/ui/src/components/Organisms/PageContent/BlockQuote.stories.ts @@ -1,5 +1,5 @@ import { Markup } from '@custom/schema'; -import Portrait from '@stories/portrait.jpg?as=metadata'; +import Avatar from '@stories/avatar.jpg?as=metadata'; import { Meta, StoryObj } from '@storybook/react'; import { image } from '../../../helpers/image'; @@ -14,7 +14,7 @@ export const Quote = { role: 'test role', author: 'Author name', image: { - source: image({ src: Portrait.src, width: 50, height: 50 }), + source: image(Avatar), alt: 'Portrait', }, quote: diff --git a/packages/ui/static/stories/avatar.jpg b/packages/ui/static/stories/avatar.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e796b545eff1cbe835334f4ddba927c3802c719b GIT binary patch literal 6782 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 W!8{yho95C(Tb0H<%=AkcX#5Ybp@(Sz literal 0 HcmV?d00001 From dfec398cb59c7e3dfddb68feb4fb988e493b6079 Mon Sep 17 00:00:00 2001 From: Luqmaan Essop Date: Thu, 18 Apr 2024 05:24:01 +0000 Subject: [PATCH 014/134] chore: update block test image check --- tests/e2e/specs/drupal/blocks.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/specs/drupal/blocks.spec.ts b/tests/e2e/specs/drupal/blocks.spec.ts index ec7dc6a1b..1f54560a8 100644 --- a/tests/e2e/specs/drupal/blocks.spec.ts +++ b/tests/e2e/specs/drupal/blocks.spec.ts @@ -27,7 +27,7 @@ test('All blocks are rendered', async ({ page }) => { page.locator( 'img:not([data-test-id=hero-image])[alt="A beautiful landscape."]', ), - ).toHaveCount(1); + ).toHaveCount(2); await expect(page.locator('figcaption:text("Media image")')).toHaveCount(1); // Video From 4002bfe2068ff18287afee1571665b3c3dc3316f Mon Sep 17 00:00:00 2001 From: Luqmaan Essop Date: Fri, 19 Apr 2024 13:02:24 +0000 Subject: [PATCH 015/134] style: spacing adjustments --- .../components/Organisms/PageContent/BlockQuote.tsx | 10 +++++----- packages/ui/src/tailwind.css | 5 +++++ 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/packages/ui/src/components/Organisms/PageContent/BlockQuote.tsx b/packages/ui/src/components/Organisms/PageContent/BlockQuote.tsx index adab18a20..6c1abaad9 100644 --- a/packages/ui/src/components/Organisms/PageContent/BlockQuote.tsx +++ b/packages/ui/src/components/Organisms/PageContent/BlockQuote.tsx @@ -29,13 +29,13 @@ export function BlockQuote(props: BlockQuoteFragment) { alt={props.image.alt || 'Author image'} /> )} -
- {props.author &&

{props.author}

} +
+ {props.author &&

{props.author}

}
{props.role && ( -

- / - +

+ / + {props.role}

diff --git a/packages/ui/src/tailwind.css b/packages/ui/src/tailwind.css index f6eb52ad0..2d464b850 100644 --- a/packages/ui/src/tailwind.css +++ b/packages/ui/src/tailwind.css @@ -11,6 +11,11 @@ padding-left: 0 !important; } +.prose :where(blockquote p):not( :where([class~='not-prose']) ) { + margin-top: 12px !important; + margin-bottom: 12px !important; +} + .prose :where(blockquote p:first-of-type):not( :where([class~='not-prose'], [class~='not-prose'] *) From d87f271abca4aec4a0d22509953198055401fc51 Mon Sep 17 00:00:00 2001 From: Vasile Chindris Date: Mon, 22 Apr 2024 14:48:09 +0300 Subject: [PATCH 016/134] test(SLB-297): update quote tests --- .../2f1be18f-dc18-4b56-8ff0-ce24d8ff7df7.yml | 27 +++++++ .../content/file/the_silverback.jpeg | Bin 0 -> 146269 bytes .../5dfc1856-e9e4-4f02-9cd6-9d888870ce1a.yml | 74 ++++++++++++++++++ .../a397ca48-8fad-411e-8901-0eba2feb989c.yml | 13 +-- .../ceb9b2a7-4c4c-4084-ada9-d5f6505d466b.yml | 4 - tests/e2e/specs/drupal/blocks.spec.ts | 10 +-- tests/schema/specs/blocks.spec.ts | 4 - 7 files changed, 109 insertions(+), 23 deletions(-) create mode 100644 packages/drupal/test_content/content/file/2f1be18f-dc18-4b56-8ff0-ce24d8ff7df7.yml create mode 100644 packages/drupal/test_content/content/file/the_silverback.jpeg create mode 100644 packages/drupal/test_content/content/media/5dfc1856-e9e4-4f02-9cd6-9d888870ce1a.yml diff --git a/packages/drupal/test_content/content/file/2f1be18f-dc18-4b56-8ff0-ce24d8ff7df7.yml b/packages/drupal/test_content/content/file/2f1be18f-dc18-4b56-8ff0-ce24d8ff7df7.yml new file mode 100644 index 000000000..8947e3bbc --- /dev/null +++ b/packages/drupal/test_content/content/file/2f1be18f-dc18-4b56-8ff0-ce24d8ff7df7.yml @@ -0,0 +1,27 @@ +_meta: + version: '1.0' + entity_type: file + uuid: 2f1be18f-dc18-4b56-8ff0-ce24d8ff7df7 + default_langcode: en +default: + uid: + - + target_id: 1 + filename: + - + value: the_silverback.jpeg + uri: + - + value: 'public://2024-04/the_silverback.jpeg' + filemime: + - + value: image/jpeg + filesize: + - + value: 146269 + status: + - + value: true + created: + - + value: 1713785099 diff --git a/packages/drupal/test_content/content/file/the_silverback.jpeg b/packages/drupal/test_content/content/file/the_silverback.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..25cc96bcef3f34d50dac92b3fb29a0d4a7e97881 GIT binary patch literal 146269 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

$cHaNN!u^wefP7j>fm116{1@N<8PkIg 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

!EV5Nfp^&=_H`_?ky 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 literal 0 HcmV?d00001 diff --git a/packages/drupal/test_content/content/media/5dfc1856-e9e4-4f02-9cd6-9d888870ce1a.yml b/packages/drupal/test_content/content/media/5dfc1856-e9e4-4f02-9cd6-9d888870ce1a.yml new file mode 100644 index 000000000..5dbede2d2 --- /dev/null +++ b/packages/drupal/test_content/content/media/5dfc1856-e9e4-4f02-9cd6-9d888870ce1a.yml @@ -0,0 +1,74 @@ +_meta: + version: '1.0' + entity_type: media + uuid: 5dfc1856-e9e4-4f02-9cd6-9d888870ce1a + bundle: image + default_langcode: en + depends: + 2f1be18f-dc18-4b56-8ff0-ce24d8ff7df7: file +default: + revision_user: + - + target_id: 1 + status: + - + value: true + uid: + - + target_id: 1 + name: + - + value: the_silverback.jpeg + created: + - + value: 1713785099 + path: + - + alias: '' + langcode: en + pathauto: 0 + content_translation_source: + - + value: und + content_translation_outdated: + - + value: false + field_media_image: + - + entity: 2f1be18f-dc18-4b56-8ff0-ce24d8ff7df7 + alt: 'The silverback' + title: '' + width: 1280 + height: 720 +translations: + de: + status: + - + value: true + uid: + - + target_id: 1 + name: + - + value: the_silverback.jpeg + created: + - + value: 1713785205 + path: + - + alias: '' + langcode: de + pathauto: 0 + content_translation_source: + - + value: en + content_translation_outdated: + - + value: false + field_media_image: + - + entity: 2f1be18f-dc18-4b56-8ff0-ce24d8ff7df7 + alt: 'The silverback DE' + title: '' + width: 1280 + height: 720 diff --git a/packages/drupal/test_content/content/node/a397ca48-8fad-411e-8901-0eba2feb989c.yml b/packages/drupal/test_content/content/node/a397ca48-8fad-411e-8901-0eba2feb989c.yml index 0ee37d6aa..095935985 100644 --- a/packages/drupal/test_content/content/node/a397ca48-8fad-411e-8901-0eba2feb989c.yml +++ b/packages/drupal/test_content/content/node/a397ca48-8fad-411e-8901-0eba2feb989c.yml @@ -8,6 +8,7 @@ _meta: 3a0fe860-a6d6-428a-9474-365bd57509aa: media 478c4289-961d-4ce8-85d6-578ae05f3019: media 72187a1f-3e48-4b45-a9b7-189c6fd7ee26: media + 5dfc1856-e9e4-4f02-9cd6-9d888870ce1a: media default: revision_uid: - @@ -76,10 +77,6 @@ default:

Heading 3

- -

Quote

Citation
- - @@ -92,7 +89,7 @@ default: - +

@@ -166,17 +163,13 @@ translations:

Heading 3 DE

- -

Quote DE

Citation DE
- - - + format: gutenberg summary: '' diff --git a/packages/drupal/test_content/content/node/ceb9b2a7-4c4c-4084-ada9-d5f6505d466b.yml b/packages/drupal/test_content/content/node/ceb9b2a7-4c4c-4084-ada9-d5f6505d466b.yml index 704d39565..c3c535376 100644 --- a/packages/drupal/test_content/content/node/ceb9b2a7-4c4c-4084-ada9-d5f6505d466b.yml +++ b/packages/drupal/test_content/content/node/ceb9b2a7-4c4c-4084-ada9-d5f6505d466b.yml @@ -58,10 +58,6 @@ default:
- -

- -

diff --git a/tests/e2e/specs/drupal/blocks.spec.ts b/tests/e2e/specs/drupal/blocks.spec.ts index 1f54560a8..727920dcd 100644 --- a/tests/e2e/specs/drupal/blocks.spec.ts +++ b/tests/e2e/specs/drupal/blocks.spec.ts @@ -27,7 +27,7 @@ test('All blocks are rendered', async ({ page }) => { page.locator( 'img:not([data-test-id=hero-image])[alt="A beautiful landscape."]', ), - ).toHaveCount(2); + ).toHaveCount(1); await expect(page.locator('figcaption:text("Media image")')).toHaveCount(1); // Video @@ -50,10 +50,10 @@ test('All blocks are rendered', async ({ page }) => { await expect(page.locator('h3:text("Heading 3")')).toHaveCount(1); // Quote - await expect(page.locator('blockquote > p:text("Quote")')).toHaveCount(1); - await expect(page.locator('blockquote > cite:text("Citation")')).toHaveCount( - 1, - ); + await expect(page.locator('blockquote:text("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus sagittis nisi nec neque porta, a ornare ligula efficitur.")')).toHaveCount(1); + await expect(page.locator('blockquote p.not-prose:text("John Doe")')).toHaveCount(1); + await expect(page.locator('blockquote p.not-prose span:text("Project manager")')).toHaveCount(1); + await expect(page.locator('blockquote img[alt="The silverback"]')).toHaveCount(1); // Form await expect( diff --git a/tests/schema/specs/blocks.spec.ts b/tests/schema/specs/blocks.spec.ts index 45eaa2e14..325018cc7 100644 --- a/tests/schema/specs/blocks.spec.ts +++ b/tests/schema/specs/blocks.spec.ts @@ -124,8 +124,6 @@ test('Blocks', async () => {
  • list 1
  • list 2
    1. list 2.2

Heading 3

- -

Quote

Citation
", }, { @@ -215,8 +213,6 @@ test('Blocks', async () => {
-

-

", }, From 045ce67858c203a522413401a680758d282a4ea3 Mon Sep 17 00:00:00 2001 From: Vasile Chindris Date: Mon, 22 Apr 2024 15:02:11 +0300 Subject: [PATCH 017/134] fix(SLB-297): prettier --- tests/e2e/specs/drupal/blocks.spec.ts | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/tests/e2e/specs/drupal/blocks.spec.ts b/tests/e2e/specs/drupal/blocks.spec.ts index 727920dcd..1e9e881dc 100644 --- a/tests/e2e/specs/drupal/blocks.spec.ts +++ b/tests/e2e/specs/drupal/blocks.spec.ts @@ -50,10 +50,20 @@ test('All blocks are rendered', async ({ page }) => { await expect(page.locator('h3:text("Heading 3")')).toHaveCount(1); // Quote - await expect(page.locator('blockquote:text("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus sagittis nisi nec neque porta, a ornare ligula efficitur.")')).toHaveCount(1); - await expect(page.locator('blockquote p.not-prose:text("John Doe")')).toHaveCount(1); - await expect(page.locator('blockquote p.not-prose span:text("Project manager")')).toHaveCount(1); - await expect(page.locator('blockquote img[alt="The silverback"]')).toHaveCount(1); + await expect( + page.locator( + 'blockquote:text("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus sagittis nisi nec neque porta, a ornare ligula efficitur.")', + ), + ).toHaveCount(1); + await expect( + page.locator('blockquote p.not-prose:text("John Doe")'), + ).toHaveCount(1); + await expect( + page.locator('blockquote p.not-prose span:text("Project manager")'), + ).toHaveCount(1); + await expect( + page.locator('blockquote img[alt="The silverback"]'), + ).toHaveCount(1); // Form await expect( From fb376946d8b6e1f938cb98f0badf1dce342ab990 Mon Sep 17 00:00:00 2001 From: Vasile Chindris Date: Tue, 23 Apr 2024 21:31:51 +0300 Subject: [PATCH 018/134] chore(SLB-339): quote editor block style --- ....entity_view_display.media.image.quote.yml | 33 +++++ .../core.entity_view_mode.media.quote.yml | 11 ++ apps/cms/config/sync/image.style.quote.yml | 15 ++ packages/drupal/gutenberg_blocks/css/edit.css | 11 +- .../gutenberg_blocks/src/blocks/quote.tsx | 132 ++++++++++-------- 5 files changed, 141 insertions(+), 61 deletions(-) create mode 100644 apps/cms/config/sync/core.entity_view_display.media.image.quote.yml create mode 100644 apps/cms/config/sync/core.entity_view_mode.media.quote.yml create mode 100644 apps/cms/config/sync/image.style.quote.yml diff --git a/apps/cms/config/sync/core.entity_view_display.media.image.quote.yml b/apps/cms/config/sync/core.entity_view_display.media.image.quote.yml new file mode 100644 index 000000000..b88657103 --- /dev/null +++ b/apps/cms/config/sync/core.entity_view_display.media.image.quote.yml @@ -0,0 +1,33 @@ +uuid: 195a40ca-6da4-4e2e-bc2f-c9c1fc0385bb +langcode: en +status: true +dependencies: + config: + - core.entity_view_mode.media.quote + - field.field.media.image.field_media_image + - image.style.quote + - media.type.image + module: + - image +id: media.image.quote +targetEntityType: media +bundle: image +mode: quote +content: + field_media_image: + type: image + label: visually_hidden + settings: + image_link: '' + image_style: quote + image_loading: + attribute: lazy + third_party_settings: { } + weight: 0 + region: content +hidden: + created: true + langcode: true + name: true + thumbnail: true + uid: true diff --git a/apps/cms/config/sync/core.entity_view_mode.media.quote.yml b/apps/cms/config/sync/core.entity_view_mode.media.quote.yml new file mode 100644 index 000000000..51148e9b4 --- /dev/null +++ b/apps/cms/config/sync/core.entity_view_mode.media.quote.yml @@ -0,0 +1,11 @@ +uuid: f54c9109-a0ff-4a09-9772-748af96b8eb0 +langcode: en +status: true +dependencies: + module: + - media +id: media.quote +label: Quote +description: '' +targetEntityType: media +cache: true diff --git a/apps/cms/config/sync/image.style.quote.yml b/apps/cms/config/sync/image.style.quote.yml new file mode 100644 index 000000000..b7a85ee89 --- /dev/null +++ b/apps/cms/config/sync/image.style.quote.yml @@ -0,0 +1,15 @@ +uuid: 983e88dc-78f8-463d-b088-026d7d96bcc0 +langcode: en +status: true +dependencies: { } +name: quote +label: 'Quote (24x24)' +effects: + 3ac18111-aeda-4cd7-aafc-5cd66d0cbf7e: + uuid: 3ac18111-aeda-4cd7-aafc-5cd66d0cbf7e + id: image_scale_and_crop + weight: 1 + data: + width: 24 + height: 24 + anchor: center-center diff --git a/packages/drupal/gutenberg_blocks/css/edit.css b/packages/drupal/gutenberg_blocks/css/edit.css index 1c603451e..39ab66473 100644 --- a/packages/drupal/gutenberg_blocks/css/edit.css +++ b/packages/drupal/gutenberg_blocks/css/edit.css @@ -7,16 +7,17 @@ margin:0; } +.gutenberg__editor blockquote .quote-image img { + object-fit: cover; + max-width: 100%; + border-radius: 9999px; +} + .gutenberg__editor blockquote::before, .gutenberg__editor blockquote::after { content: ''; } -.gutenberg__editor blockquote .quote-author, -.gutenberg__editor blockquote .quote-role { - text-align: right; -} - .gutenberg__editor .container-wrapper { display: block; position: relative; diff --git a/packages/drupal/gutenberg_blocks/src/blocks/quote.tsx b/packages/drupal/gutenberg_blocks/src/blocks/quote.tsx index 1aea31b6a..d5873d2d7 100644 --- a/packages/drupal/gutenberg_blocks/src/blocks/quote.tsx +++ b/packages/drupal/gutenberg_blocks/src/blocks/quote.tsx @@ -35,63 +35,83 @@ registerBlockType(`custom/quote`, { // @ts-ignore edit: compose(withState())((props) => { return ( -
- { - props.setAttributes({ - quote: cleanUpText(quote, ['strong']), - }); - }} - /> - { - setPlainTextAttribute(props, 'author', author); - }} - /> - { - setPlainTextAttribute(props, 'role', role); - }} - /> - { +
+
+ + + + -
+ disableLineBreaks={false} + placeholder={__('Quote')} + keepPlaceholderOnFocus={false} + onChange={(quote) => { + props.setAttributes({ + quote: cleanUpText(quote, ['strong']), + }); + }} + /> +
+
+ { + // @ts-ignore + error = typeof error === 'string' ? error : error[2]; + dispatch('core/notices').createWarningNotice(error); + }} + /> +
+ { + setPlainTextAttribute(props, 'author', author); + }} + /> + / + { + setPlainTextAttribute(props, 'role', role); + }} + /> +
+
+
); }), save() { From d267f30dc4bc47f81543cf4eefe20f70543557b5 Mon Sep 17 00:00:00 2001 From: Vasile Chindris Date: Tue, 23 Apr 2024 21:31:51 +0300 Subject: [PATCH 019/134] chore(SLB-339): quote editor block style --- ....entity_view_display.media.image.quote.yml | 33 +++++ .../core.entity_view_mode.media.quote.yml | 11 ++ apps/cms/config/sync/image.style.quote.yml | 15 ++ packages/drupal/gutenberg_blocks/css/edit.css | 11 +- .../gutenberg_blocks/src/blocks/quote.tsx | 132 ++++++++++-------- 5 files changed, 141 insertions(+), 61 deletions(-) create mode 100644 apps/cms/config/sync/core.entity_view_display.media.image.quote.yml create mode 100644 apps/cms/config/sync/core.entity_view_mode.media.quote.yml create mode 100644 apps/cms/config/sync/image.style.quote.yml diff --git a/apps/cms/config/sync/core.entity_view_display.media.image.quote.yml b/apps/cms/config/sync/core.entity_view_display.media.image.quote.yml new file mode 100644 index 000000000..b88657103 --- /dev/null +++ b/apps/cms/config/sync/core.entity_view_display.media.image.quote.yml @@ -0,0 +1,33 @@ +uuid: 195a40ca-6da4-4e2e-bc2f-c9c1fc0385bb +langcode: en +status: true +dependencies: + config: + - core.entity_view_mode.media.quote + - field.field.media.image.field_media_image + - image.style.quote + - media.type.image + module: + - image +id: media.image.quote +targetEntityType: media +bundle: image +mode: quote +content: + field_media_image: + type: image + label: visually_hidden + settings: + image_link: '' + image_style: quote + image_loading: + attribute: lazy + third_party_settings: { } + weight: 0 + region: content +hidden: + created: true + langcode: true + name: true + thumbnail: true + uid: true diff --git a/apps/cms/config/sync/core.entity_view_mode.media.quote.yml b/apps/cms/config/sync/core.entity_view_mode.media.quote.yml new file mode 100644 index 000000000..51148e9b4 --- /dev/null +++ b/apps/cms/config/sync/core.entity_view_mode.media.quote.yml @@ -0,0 +1,11 @@ +uuid: f54c9109-a0ff-4a09-9772-748af96b8eb0 +langcode: en +status: true +dependencies: + module: + - media +id: media.quote +label: Quote +description: '' +targetEntityType: media +cache: true diff --git a/apps/cms/config/sync/image.style.quote.yml b/apps/cms/config/sync/image.style.quote.yml new file mode 100644 index 000000000..b7a85ee89 --- /dev/null +++ b/apps/cms/config/sync/image.style.quote.yml @@ -0,0 +1,15 @@ +uuid: 983e88dc-78f8-463d-b088-026d7d96bcc0 +langcode: en +status: true +dependencies: { } +name: quote +label: 'Quote (24x24)' +effects: + 3ac18111-aeda-4cd7-aafc-5cd66d0cbf7e: + uuid: 3ac18111-aeda-4cd7-aafc-5cd66d0cbf7e + id: image_scale_and_crop + weight: 1 + data: + width: 24 + height: 24 + anchor: center-center diff --git a/packages/drupal/gutenberg_blocks/css/edit.css b/packages/drupal/gutenberg_blocks/css/edit.css index 1c603451e..39ab66473 100644 --- a/packages/drupal/gutenberg_blocks/css/edit.css +++ b/packages/drupal/gutenberg_blocks/css/edit.css @@ -7,16 +7,17 @@ margin:0; } +.gutenberg__editor blockquote .quote-image img { + object-fit: cover; + max-width: 100%; + border-radius: 9999px; +} + .gutenberg__editor blockquote::before, .gutenberg__editor blockquote::after { content: ''; } -.gutenberg__editor blockquote .quote-author, -.gutenberg__editor blockquote .quote-role { - text-align: right; -} - .gutenberg__editor .container-wrapper { display: block; position: relative; diff --git a/packages/drupal/gutenberg_blocks/src/blocks/quote.tsx b/packages/drupal/gutenberg_blocks/src/blocks/quote.tsx index 1aea31b6a..d5873d2d7 100644 --- a/packages/drupal/gutenberg_blocks/src/blocks/quote.tsx +++ b/packages/drupal/gutenberg_blocks/src/blocks/quote.tsx @@ -35,63 +35,83 @@ registerBlockType(`custom/quote`, { // @ts-ignore edit: compose(withState())((props) => { return ( -
- { - props.setAttributes({ - quote: cleanUpText(quote, ['strong']), - }); - }} - /> - { - setPlainTextAttribute(props, 'author', author); - }} - /> - { - setPlainTextAttribute(props, 'role', role); - }} - /> - { +
+
+ + + + -
+ disableLineBreaks={false} + placeholder={__('Quote')} + keepPlaceholderOnFocus={false} + onChange={(quote) => { + props.setAttributes({ + quote: cleanUpText(quote, ['strong']), + }); + }} + /> +
+
+ { + // @ts-ignore + error = typeof error === 'string' ? error : error[2]; + dispatch('core/notices').createWarningNotice(error); + }} + /> +
+ { + setPlainTextAttribute(props, 'author', author); + }} + /> + / + { + setPlainTextAttribute(props, 'role', role); + }} + /> +
+
+ ); }), save() { From 7f2d44f37b327c86b512fcb432240eb858905d8c Mon Sep 17 00:00:00 2001 From: Luqmaan Essop Date: Wed, 24 Apr 2024 08:39:18 +0200 Subject: [PATCH 020/134] style: adjust margin --- .../ui/src/components/Organisms/PageContent/BlockQuote.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/components/Organisms/PageContent/BlockQuote.tsx b/packages/ui/src/components/Organisms/PageContent/BlockQuote.tsx index 6c1abaad9..d4f84abe1 100644 --- a/packages/ui/src/components/Organisms/PageContent/BlockQuote.tsx +++ b/packages/ui/src/components/Organisms/PageContent/BlockQuote.tsx @@ -24,12 +24,12 @@ export function BlockQuote(props: BlockQuoteFragment) {
{props.image && ( {props.image.alt )} -
+
{props.author &&

{props.author}

}
{props.role && ( From 12f141bcd3a8ea0a902a12c4e872ca8361e404e3 Mon Sep 17 00:00:00 2001 From: Luqmaan Essop Date: Wed, 24 Apr 2024 09:13:30 +0200 Subject: [PATCH 021/134] style(SLB-298): add no author avatar block quote story --- .../Organisms/PageContent/BlockQuote.stories.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/ui/src/components/Organisms/PageContent/BlockQuote.stories.ts b/packages/ui/src/components/Organisms/PageContent/BlockQuote.stories.ts index ffe6090f1..6b40c8afd 100644 --- a/packages/ui/src/components/Organisms/PageContent/BlockQuote.stories.ts +++ b/packages/ui/src/components/Organisms/PageContent/BlockQuote.stories.ts @@ -21,3 +21,12 @@ export const Quote = { '

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

' as Markup, }, } satisfies StoryObj; + +export const NoAvatarQuote = { + args: { + role: 'test role', + author: 'Author name', + quote: + '

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

' as Markup, + }, +} satisfies StoryObj; From a07a80efa99ace96c31113dbe9ae5f2fcd2cc8db Mon Sep 17 00:00:00 2001 From: Luqmaan Essop Date: Wed, 24 Apr 2024 08:39:18 +0200 Subject: [PATCH 022/134] style: adjust margin --- .../ui/src/components/Organisms/PageContent/BlockQuote.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/components/Organisms/PageContent/BlockQuote.tsx b/packages/ui/src/components/Organisms/PageContent/BlockQuote.tsx index 6c1abaad9..d4f84abe1 100644 --- a/packages/ui/src/components/Organisms/PageContent/BlockQuote.tsx +++ b/packages/ui/src/components/Organisms/PageContent/BlockQuote.tsx @@ -24,12 +24,12 @@ export function BlockQuote(props: BlockQuoteFragment) {
{props.image && ( {props.image.alt )} -
+
{props.author &&

{props.author}

}
{props.role && ( From fad43f79db52943038fd52a6cf48f65720f2fbf8 Mon Sep 17 00:00:00 2001 From: Luqmaan Essop Date: Wed, 24 Apr 2024 09:13:30 +0200 Subject: [PATCH 023/134] style(SLB-298): add no author avatar block quote story --- .../Organisms/PageContent/BlockQuote.stories.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/ui/src/components/Organisms/PageContent/BlockQuote.stories.ts b/packages/ui/src/components/Organisms/PageContent/BlockQuote.stories.ts index ffe6090f1..6b40c8afd 100644 --- a/packages/ui/src/components/Organisms/PageContent/BlockQuote.stories.ts +++ b/packages/ui/src/components/Organisms/PageContent/BlockQuote.stories.ts @@ -21,3 +21,12 @@ export const Quote = { '

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

' as Markup, }, } satisfies StoryObj; + +export const NoAvatarQuote = { + args: { + role: 'test role', + author: 'Author name', + quote: + '

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

' as Markup, + }, +} satisfies StoryObj; From aba7e01561e83e6cc0a9719e265a2bb72ee43385 Mon Sep 17 00:00:00 2001 From: Luqmaan Essop Date: Wed, 24 Apr 2024 10:41:49 +0200 Subject: [PATCH 024/134] style(SLB-298): add blockquote padding --- packages/ui/src/components/Organisms/PageContent/BlockQuote.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/src/components/Organisms/PageContent/BlockQuote.tsx b/packages/ui/src/components/Organisms/PageContent/BlockQuote.tsx index d4f84abe1..7ad1d3ab0 100644 --- a/packages/ui/src/components/Organisms/PageContent/BlockQuote.tsx +++ b/packages/ui/src/components/Organisms/PageContent/BlockQuote.tsx @@ -4,7 +4,7 @@ import React from 'react'; export function BlockQuote(props: BlockQuoteFragment) { return (
-
+
Date: Wed, 24 Apr 2024 13:04:48 +0300 Subject: [PATCH 025/134] fix: update schema tests --- tests/schema/specs/blocks.spec.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/schema/specs/blocks.spec.ts b/tests/schema/specs/blocks.spec.ts index 14f60fb00..7dabb59f9 100644 --- a/tests/schema/specs/blocks.spec.ts +++ b/tests/schema/specs/blocks.spec.ts @@ -271,6 +271,7 @@ test('Blocks', async () => { "markup": "

", + }, }, { "__typename": "BlockQuote", From 1564a4eb81cae6c5933f8d473aeee9b8a84883fa Mon Sep 17 00:00:00 2001 From: Luqmaan Essop Date: Wed, 24 Apr 2024 10:41:49 +0200 Subject: [PATCH 026/134] style(SLB-298): add blockquote padding --- packages/ui/src/components/Organisms/PageContent/BlockQuote.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/src/components/Organisms/PageContent/BlockQuote.tsx b/packages/ui/src/components/Organisms/PageContent/BlockQuote.tsx index d4f84abe1..7ad1d3ab0 100644 --- a/packages/ui/src/components/Organisms/PageContent/BlockQuote.tsx +++ b/packages/ui/src/components/Organisms/PageContent/BlockQuote.tsx @@ -4,7 +4,7 @@ import React from 'react'; export function BlockQuote(props: BlockQuoteFragment) { return (
-
+
Date: Thu, 25 Apr 2024 09:32:20 +0200 Subject: [PATCH 027/134] style(SLB-298): typography tweaks --- .../ui/src/components/Organisms/PageContent/BlockQuote.tsx | 6 +++--- packages/ui/src/tailwind.css | 7 ++++++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/packages/ui/src/components/Organisms/PageContent/BlockQuote.tsx b/packages/ui/src/components/Organisms/PageContent/BlockQuote.tsx index 7ad1d3ab0..c99955e06 100644 --- a/packages/ui/src/components/Organisms/PageContent/BlockQuote.tsx +++ b/packages/ui/src/components/Organisms/PageContent/BlockQuote.tsx @@ -3,7 +3,7 @@ import React from 'react'; export function BlockQuote(props: BlockQuoteFragment) { return ( -
+
- {props.quote && } +

{props.quote && }

{props.image && ( {props.image.alt )} -
+
{props.author &&

{props.author}

}
{props.role && ( diff --git a/packages/ui/src/tailwind.css b/packages/ui/src/tailwind.css index 2d464b850..944e19871 100644 --- a/packages/ui/src/tailwind.css +++ b/packages/ui/src/tailwind.css @@ -6,12 +6,17 @@ @tailwind utilities; /* Prose overrides */ +.prose + :where(blockquote):not(:where([class~='not-prose'], [class~='not-prose'] *)) { + font-weight: 400; +} + .lg\:prose-xl :where(blockquote):not(:where([class~='not-prose'], [class~='not-prose'] *)) { padding-left: 0 !important; } -.prose :where(blockquote p):not( :where([class~='not-prose']) ) { +.prose :where(blockquote p):not(:where([class~='not-prose'])) { margin-top: 12px !important; margin-bottom: 12px !important; } From e7148d0a50d9deac662f9f2df07afa8596aa64cc Mon Sep 17 00:00:00 2001 From: Luqmaan Essop Date: Thu, 25 Apr 2024 10:27:10 +0200 Subject: [PATCH 028/134] chore: update tests --- tests/e2e/specs/drupal/blocks.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/specs/drupal/blocks.spec.ts b/tests/e2e/specs/drupal/blocks.spec.ts index a498b98d7..5124a81b2 100644 --- a/tests/e2e/specs/drupal/blocks.spec.ts +++ b/tests/e2e/specs/drupal/blocks.spec.ts @@ -52,7 +52,7 @@ test('All blocks are rendered', async ({ page }) => { // Quote await expect( page.locator( - 'blockquote:text("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus sagittis nisi nec neque porta, a ornare ligula efficitur.")', + 'blockquote p:text("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus sagittis nisi nec neque porta, a ornare ligula efficitur.")', ), ).toHaveCount(1); await expect( From 07ec3af7fea40ff3abcf0fd61bcc1a8da1bb7bf4 Mon Sep 17 00:00:00 2001 From: Vasile Chindris Date: Mon, 29 Apr 2024 17:25:04 +0300 Subject: [PATCH 029/134] feat(SLB-292): horizontal separator gutenberg block --- .../src/blocks/horizontal-separator.tsx | 21 +++++++++++++++++++ packages/drupal/gutenberg_blocks/src/index.ts | 1 + .../a397ca48-8fad-411e-8901-0eba2feb989c.yml | 2 ++ packages/schema/src/fragments/Page.gql | 1 + .../PageContent/BlockHorizontalSeparator.gql | 5 +++++ packages/schema/src/schema.graphql | 5 +++++ .../BlockHorizontalSeparator.stories.ts | 11 ++++++++++ .../PageContent/BlockHorizontalSeparator.tsx | 5 +++++ .../src/components/Organisms/PageDisplay.tsx | 3 +++ tests/e2e/specs/drupal/blocks.spec.ts | 3 +++ tests/schema/specs/blocks.spec.ts | 6 ++++++ 11 files changed, 63 insertions(+) create mode 100644 packages/drupal/gutenberg_blocks/src/blocks/horizontal-separator.tsx create mode 100644 packages/schema/src/fragments/PageContent/BlockHorizontalSeparator.gql create mode 100644 packages/ui/src/components/Organisms/PageContent/BlockHorizontalSeparator.stories.ts create mode 100644 packages/ui/src/components/Organisms/PageContent/BlockHorizontalSeparator.tsx diff --git a/packages/drupal/gutenberg_blocks/src/blocks/horizontal-separator.tsx b/packages/drupal/gutenberg_blocks/src/blocks/horizontal-separator.tsx new file mode 100644 index 000000000..720094b1f --- /dev/null +++ b/packages/drupal/gutenberg_blocks/src/blocks/horizontal-separator.tsx @@ -0,0 +1,21 @@ +import { registerBlockType } from 'wordpress__blocks'; +import { compose, withState } from 'wordpress__compose'; + +declare const Drupal: { t: (s: string) => string }; + +const { t: __ } = Drupal; + +// @ts-ignore +registerBlockType(`custom/horizontal-separator`, { + title: __('Horizontal separator'), + icon: 'minus', + category: 'text', + attributes: {}, + // @ts-ignore + edit: compose(withState())(() => { + return
; + }), + save() { + return null; + }, +}); diff --git a/packages/drupal/gutenberg_blocks/src/index.ts b/packages/drupal/gutenberg_blocks/src/index.ts index 2763bdad8..2c86b28df 100644 --- a/packages/drupal/gutenberg_blocks/src/index.ts +++ b/packages/drupal/gutenberg_blocks/src/index.ts @@ -9,3 +9,4 @@ import './blocks/image-with-text'; import './filters/list'; import './blocks/cta'; import './blocks/quote'; +import './blocks/horizontal-separator'; diff --git a/packages/drupal/test_content/content/node/a397ca48-8fad-411e-8901-0eba2feb989c.yml b/packages/drupal/test_content/content/node/a397ca48-8fad-411e-8901-0eba2feb989c.yml index ebd66747b..cc7765c07 100644 --- a/packages/drupal/test_content/content/node/a397ca48-8fad-411e-8901-0eba2feb989c.yml +++ b/packages/drupal/test_content/content/node/a397ca48-8fad-411e-8901-0eba2feb989c.yml @@ -55,6 +55,8 @@ default:

A standalone paragraph with markup and link

+ + diff --git a/packages/schema/src/fragments/Page.gql b/packages/schema/src/fragments/Page.gql index cbf2227eb..3ecddb3c9 100644 --- a/packages/schema/src/fragments/Page.gql +++ b/packages/schema/src/fragments/Page.gql @@ -35,6 +35,7 @@ fragment Page on Page { ...BlockCta ...BlockImageWithText ...BlockQuote + ...BlockHorizontalSeparator } metaTags { tag diff --git a/packages/schema/src/fragments/PageContent/BlockHorizontalSeparator.gql b/packages/schema/src/fragments/PageContent/BlockHorizontalSeparator.gql new file mode 100644 index 000000000..ef992a139 --- /dev/null +++ b/packages/schema/src/fragments/PageContent/BlockHorizontalSeparator.gql @@ -0,0 +1,5 @@ +fragment BlockHorizontalSeparator on BlockHorizontalSeparator { + # This is not really used, but until we have other real setting fields, we + # have to provide a dummy one. + markup +} diff --git a/packages/schema/src/schema.graphql b/packages/schema/src/schema.graphql index ca52d0ef3..d367ab9c4 100644 --- a/packages/schema/src/schema.graphql +++ b/packages/schema/src/schema.graphql @@ -206,6 +206,7 @@ union PageContent @resolveEditorBlockType = | BlockCta | BlockImageWithText | BlockQuote + | BlockHorizontalSeparator type BlockForm @type(id: "custom/form") { url: Url @resolveEditorBlockAttribute(key: "formId") @webformIdToUrl(id: "$") @@ -270,6 +271,10 @@ type BlockQuote @type(id: "custom/quote") { image: MediaImage @resolveEditorBlockMedia } +type BlockHorizontalSeparator @type(id: "custom/horizontal-separator") { + markup: Markup! @resolveEditorBlockMarkup +} + input PaginationInput { limit: Int! offset: Int! diff --git a/packages/ui/src/components/Organisms/PageContent/BlockHorizontalSeparator.stories.ts b/packages/ui/src/components/Organisms/PageContent/BlockHorizontalSeparator.stories.ts new file mode 100644 index 000000000..006c3158f --- /dev/null +++ b/packages/ui/src/components/Organisms/PageContent/BlockHorizontalSeparator.stories.ts @@ -0,0 +1,11 @@ +import { Meta, StoryObj } from '@storybook/react'; + +import { BlockHorizontalSeparator } from './BlockHorizontalSeparator'; + +export default { + component: BlockHorizontalSeparator, +} satisfies Meta; + +export const HorizontalSeparator = { + args: {}, +} satisfies StoryObj; diff --git a/packages/ui/src/components/Organisms/PageContent/BlockHorizontalSeparator.tsx b/packages/ui/src/components/Organisms/PageContent/BlockHorizontalSeparator.tsx new file mode 100644 index 000000000..4d5308927 --- /dev/null +++ b/packages/ui/src/components/Organisms/PageContent/BlockHorizontalSeparator.tsx @@ -0,0 +1,5 @@ +import React from 'react'; + +export function BlockHorizontalSeparator() { + return
; +} diff --git a/packages/ui/src/components/Organisms/PageDisplay.tsx b/packages/ui/src/components/Organisms/PageDisplay.tsx index 86813de03..f5b15c390 100644 --- a/packages/ui/src/components/Organisms/PageDisplay.tsx +++ b/packages/ui/src/components/Organisms/PageDisplay.tsx @@ -6,6 +6,7 @@ import { UnreachableCaseError } from '../../utils/unreachable-case-error'; import { PageTransition } from '../Molecules/PageTransition'; import { BlockCta } from './PageContent/BlockCta'; import { BlockForm } from './PageContent/BlockForm'; +import { BlockHorizontalSeparator } from './PageContent/BlockHorizontalSeparator'; import { BlockMarkup } from './PageContent/BlockMarkup'; import { BlockMedia } from './PageContent/BlockMedia'; import { BlockQuote } from './PageContent/BlockQuote'; @@ -60,6 +61,8 @@ export function PageDisplay(page: PageFragment) { ); case 'BlockQuote': return
; + case 'BlockHorizontalSeparator': + return ; default: throw new UnreachableCaseError(block); } diff --git a/tests/e2e/specs/drupal/blocks.spec.ts b/tests/e2e/specs/drupal/blocks.spec.ts index c3c775333..33be85ba3 100644 --- a/tests/e2e/specs/drupal/blocks.spec.ts +++ b/tests/e2e/specs/drupal/blocks.spec.ts @@ -22,6 +22,9 @@ test('All blocks are rendered', async ({ page }) => { page.locator('a:text("link")[href="/en/architecture"]'), ).toHaveCount(1); + // Horizontal separator. + await expect(page.locator('hr')).toHaveCount(1); + // Image await expect( page.locator( diff --git a/tests/schema/specs/blocks.spec.ts b/tests/schema/specs/blocks.spec.ts index 4730f8d2a..866c376a4 100644 --- a/tests/schema/specs/blocks.spec.ts +++ b/tests/schema/specs/blocks.spec.ts @@ -71,6 +71,9 @@ test('Blocks', async () => { __typename } } + ... on BlockHorizontalSeparator { + __typename + } } } { @@ -106,6 +109,9 @@ test('Blocks', async () => {

A standalone paragraph with markup and link

", }, + { + "__typename": "BlockHorizontalSeparator", + }, { "__typename": "BlockMedia", "caption": "Media image", From 74e61e9591d412ef67a87c40c4279393a8ae0812 Mon Sep 17 00:00:00 2001 From: Christophe Jossart Date: Fri, 3 May 2024 11:11:10 +0200 Subject: [PATCH 030/134] feat(SLB-300): add accordion block --- .../src/blocks/accordion-item-text.tsx | 90 +++++++++++++++++++ .../gutenberg_blocks/src/blocks/accordion.tsx | 25 ++++++ packages/drupal/gutenberg_blocks/src/index.ts | 2 + 3 files changed, 117 insertions(+) create mode 100644 packages/drupal/gutenberg_blocks/src/blocks/accordion-item-text.tsx create mode 100644 packages/drupal/gutenberg_blocks/src/blocks/accordion.tsx diff --git a/packages/drupal/gutenberg_blocks/src/blocks/accordion-item-text.tsx b/packages/drupal/gutenberg_blocks/src/blocks/accordion-item-text.tsx new file mode 100644 index 000000000..774c0a96c --- /dev/null +++ b/packages/drupal/gutenberg_blocks/src/blocks/accordion-item-text.tsx @@ -0,0 +1,90 @@ +import React, { Fragment } from 'react'; +import { InspectorControls, RichText } from 'wordpress__block-editor'; +import { registerBlockType } from 'wordpress__blocks'; +import { PanelBody, SelectControl } from 'wordpress__components'; +import { compose, withState } from 'wordpress__compose'; + +// @ts-ignore +const { t: __ } = Drupal; +// @ts-ignore +registerBlockType('custom/accordion-item-text', { + title: 'Accordion Item Text', + icon: 'text', + category: 'layout', + parent: ['custom/accordion'], + attributes: { + title: { + type: 'string', + }, + text: { + type: 'string', + }, + icon: { + type: 'string', + }, + }, + // @ts-ignore + edit: compose(withState())((props) => { + const { attributes, setAttributes } = props; + const icons = [ + { label: __('- Select an optional icon -'), value: '' }, + { label: __('Checkmark'), value: 'checkmark' }, + { label: __('Questionmark'), value: 'questionmark' }, + { label: __('Arrow'), value: 'arrow' }, + ]; + setAttributes({ + icon: attributes.icon === undefined ? '' : attributes.icon, + }); + + return ( + + + + { + setAttributes({ + icon: newValue, + }); + }} + /> + + +
+
{__('Accordion Item Text')}
+
+ { + setAttributes({ title: newValue }); + }} + /> + { + setAttributes({ text: newValue }); + }} + /> +
+
+
+ ); + }), + + save() { + return null; + }, +}); diff --git a/packages/drupal/gutenberg_blocks/src/blocks/accordion.tsx b/packages/drupal/gutenberg_blocks/src/blocks/accordion.tsx new file mode 100644 index 000000000..924746dd8 --- /dev/null +++ b/packages/drupal/gutenberg_blocks/src/blocks/accordion.tsx @@ -0,0 +1,25 @@ +import { InnerBlocks } from 'wordpress__block-editor'; +import { registerBlockType } from 'wordpress__blocks'; + +// @ts-ignore +const { t: __ } = Drupal; + +registerBlockType('custom/accordion', { + title: __('Accordion'), + icon: 'menu', + category: 'layout', + attributes: {}, + edit: () => { + return ( +
+
{__('Accordion')}
+ +
+ ); + }, + save: () => , +}); diff --git a/packages/drupal/gutenberg_blocks/src/index.ts b/packages/drupal/gutenberg_blocks/src/index.ts index 2763bdad8..1a936eb57 100644 --- a/packages/drupal/gutenberg_blocks/src/index.ts +++ b/packages/drupal/gutenberg_blocks/src/index.ts @@ -9,3 +9,5 @@ import './blocks/image-with-text'; import './filters/list'; import './blocks/cta'; import './blocks/quote'; +import './blocks/accordion'; +import './blocks/accordion-item-text'; From abec41f839406203bf54cb7c061a80be6e2b32d5 Mon Sep 17 00:00:00 2001 From: Christophe Jossart Date: Fri, 3 May 2024 11:30:28 +0200 Subject: [PATCH 031/134] feat(SLB-300): accordion validators --- .../AccordionItemTextValidator.php | 37 ++++++++++++++++++ .../GutenbergValidator/AccordionValidator.php | 39 +++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 packages/drupal/gutenberg_blocks/src/Plugin/Validation/GutenbergValidator/AccordionItemTextValidator.php create mode 100644 packages/drupal/gutenberg_blocks/src/Plugin/Validation/GutenbergValidator/AccordionValidator.php diff --git a/packages/drupal/gutenberg_blocks/src/Plugin/Validation/GutenbergValidator/AccordionItemTextValidator.php b/packages/drupal/gutenberg_blocks/src/Plugin/Validation/GutenbergValidator/AccordionItemTextValidator.php new file mode 100644 index 000000000..6f9a6dcee --- /dev/null +++ b/packages/drupal/gutenberg_blocks/src/Plugin/Validation/GutenbergValidator/AccordionItemTextValidator.php @@ -0,0 +1,37 @@ + [ + 'field_label' => $this->t('Title'), + 'rules' => ['required'], + ], + // @todo check if we want text as rich text or inner blocks. + ]; + } + +} diff --git a/packages/drupal/gutenberg_blocks/src/Plugin/Validation/GutenbergValidator/AccordionValidator.php b/packages/drupal/gutenberg_blocks/src/Plugin/Validation/GutenbergValidator/AccordionValidator.php new file mode 100644 index 000000000..6e7443e53 --- /dev/null +++ b/packages/drupal/gutenberg_blocks/src/Plugin/Validation/GutenbergValidator/AccordionValidator.php @@ -0,0 +1,39 @@ + 'custom/accordion-item-text', + 'blockLabel' => $this->t('Accordion'), + 'min' => 1, + 'max' => GutenbergCardinalityValidatorInterface::CARDINALITY_UNLIMITED, + ], + ]; + return $this->validateCardinality($block, $expectedChildren); + } + +} From b6a65f4f4f489e858c1025fa0812df13ffa74f4a Mon Sep 17 00:00:00 2001 From: Christophe Jossart Date: Fri, 3 May 2024 11:43:19 +0200 Subject: [PATCH 032/134] feat(SLB-300): accordion schema --- packages/schema/src/schema.graphql | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/packages/schema/src/schema.graphql b/packages/schema/src/schema.graphql index ca52d0ef3..1114a2cea 100644 --- a/packages/schema/src/schema.graphql +++ b/packages/schema/src/schema.graphql @@ -206,6 +206,7 @@ union PageContent @resolveEditorBlockType = | BlockCta | BlockImageWithText | BlockQuote + | BlockAccordion type BlockForm @type(id: "custom/form") { url: Url @resolveEditorBlockAttribute(key: "formId") @webformIdToUrl(id: "$") @@ -245,6 +246,24 @@ type BlockImageTeaser @default @value { ctaUrl: Url @resolveEditorBlockAttribute(key: "ctaUrl") } +type BlockAccordion @type(id: "custom/accordion") { + items: [BlockAccordionItem]! @resolveEditorBlockChildren +} + +interface BlockAccordionItemInterface { + title: String! +} + +union BlockAccordionItem @resolveEditorBlockType = BlockAccordionItemText + +type BlockAccordionItemText implements BlockAccordionItemInterface + @type(id: "custom/accordion-item-text") { + title: String! @resolveEditorBlockAttribute(key: "title") + icon: String! @resolveEditorBlockAttribute(key: "icon") + # @todo check if we want inner blocks here. + text: String! @resolveEditorBlockAttribute(key: "text") +} + type BlockCta @type(id: "custom/cta") { url: Url @resolveEditorBlockAttribute(key: "url") text: String @resolveEditorBlockAttribute(key: "text") From cca79348f01e9356df9fc7778abe414f93f5555a Mon Sep 17 00:00:00 2001 From: Christophe Jossart Date: Fri, 3 May 2024 12:38:36 +0200 Subject: [PATCH 033/134] chore(SLB-300): add placeholder for storybook integration --- packages/ui/src/components/Organisms/PageDisplay.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/ui/src/components/Organisms/PageDisplay.tsx b/packages/ui/src/components/Organisms/PageDisplay.tsx index 86813de03..6369f68f6 100644 --- a/packages/ui/src/components/Organisms/PageDisplay.tsx +++ b/packages/ui/src/components/Organisms/PageDisplay.tsx @@ -60,6 +60,10 @@ export function PageDisplay(page: PageFragment) { ); case 'BlockQuote': return
; + case 'BlockAccordion': + // @todo implement. + // eslint-disable-next-line react/jsx-no-literals + return
BlockAccordion goes here
; default: throw new UnreachableCaseError(block); } From 7d5cd0674ae4b7dbcf62ab553752e271a03439d5 Mon Sep 17 00:00:00 2001 From: Christophe Jossart Date: Fri, 3 May 2024 13:09:06 +0200 Subject: [PATCH 034/134] feat(SLB-300): add accordion fragments --- packages/schema/src/fragments/Page.gql | 1 + .../src/fragments/PageContent/BlockAccordion.gql | 11 +++++++++++ 2 files changed, 12 insertions(+) create mode 100644 packages/schema/src/fragments/PageContent/BlockAccordion.gql diff --git a/packages/schema/src/fragments/Page.gql b/packages/schema/src/fragments/Page.gql index cbf2227eb..53a741be7 100644 --- a/packages/schema/src/fragments/Page.gql +++ b/packages/schema/src/fragments/Page.gql @@ -35,6 +35,7 @@ fragment Page on Page { ...BlockCta ...BlockImageWithText ...BlockQuote + ...BlockAccordion } metaTags { tag diff --git a/packages/schema/src/fragments/PageContent/BlockAccordion.gql b/packages/schema/src/fragments/PageContent/BlockAccordion.gql new file mode 100644 index 000000000..d98beaa51 --- /dev/null +++ b/packages/schema/src/fragments/PageContent/BlockAccordion.gql @@ -0,0 +1,11 @@ +fragment BlockAccordion on BlockAccordion { + items { + ...BlockAccordionItemText + } +} + +fragment BlockAccordionItemText on BlockAccordionItemText { + title + icon + text +} From 8b75635e9e001b1ed6b7f7755d85458e5b5566f0 Mon Sep 17 00:00:00 2001 From: Christophe Jossart Date: Fri, 3 May 2024 14:16:18 +0200 Subject: [PATCH 035/134] fix(SLB-300): don't use union for now Types implementing queryable interfaces must also implement the Node interface. --- packages/schema/src/schema.graphql | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/packages/schema/src/schema.graphql b/packages/schema/src/schema.graphql index 1114a2cea..c65d6f320 100644 --- a/packages/schema/src/schema.graphql +++ b/packages/schema/src/schema.graphql @@ -247,17 +247,10 @@ type BlockImageTeaser @default @value { } type BlockAccordion @type(id: "custom/accordion") { - items: [BlockAccordionItem]! @resolveEditorBlockChildren + items: [BlockAccordionItemText]! @resolveEditorBlockChildren } -interface BlockAccordionItemInterface { - title: String! -} - -union BlockAccordionItem @resolveEditorBlockType = BlockAccordionItemText - -type BlockAccordionItemText implements BlockAccordionItemInterface - @type(id: "custom/accordion-item-text") { +type BlockAccordionItemText @default @value { title: String! @resolveEditorBlockAttribute(key: "title") icon: String! @resolveEditorBlockAttribute(key: "icon") # @todo check if we want inner blocks here. From f93cc5e0fbe8ca4a99c0d3118bb49dc9fd0c2c1c Mon Sep 17 00:00:00 2001 From: Christophe Jossart Date: Fri, 3 May 2024 15:31:00 +0200 Subject: [PATCH 036/134] refactor(SLB-300): use InnerBlocks instead of RichText --- .../AccordionItemTextValidator.php | 16 ++++++++++- .../GutenbergValidator/AccordionValidator.php | 3 +++ .../src/blocks/accordion-item-text.tsx | 27 ++++++++----------- .../fragments/PageContent/BlockAccordion.gql | 4 ++- packages/schema/src/schema.graphql | 3 +-- 5 files changed, 33 insertions(+), 20 deletions(-) diff --git a/packages/drupal/gutenberg_blocks/src/Plugin/Validation/GutenbergValidator/AccordionItemTextValidator.php b/packages/drupal/gutenberg_blocks/src/Plugin/Validation/GutenbergValidator/AccordionItemTextValidator.php index 6f9a6dcee..4ab6b31ab 100644 --- a/packages/drupal/gutenberg_blocks/src/Plugin/Validation/GutenbergValidator/AccordionItemTextValidator.php +++ b/packages/drupal/gutenberg_blocks/src/Plugin/Validation/GutenbergValidator/AccordionItemTextValidator.php @@ -3,6 +3,8 @@ namespace Drupal\gutenberg_blocks\Plugin\Validation\GutenbergValidator; use Drupal\Core\StringTranslation\StringTranslationTrait; +use Drupal\silverback_gutenberg\GutenbergValidation\GutenbergCardinalityValidatorInterface; +use Drupal\silverback_gutenberg\GutenbergValidation\GutenbergCardinalityValidatorTrait; use Drupal\silverback_gutenberg\GutenbergValidation\GutenbergValidatorBase; /** @@ -12,6 +14,7 @@ * ) */ class AccordionItemTextValidator extends GutenbergValidatorBase { + use GutenbergCardinalityValidatorTrait; use StringTranslationTrait; /** @@ -30,8 +33,19 @@ public function validatedFields($block = []): array { 'field_label' => $this->t('Title'), 'rules' => ['required'], ], - // @todo check if we want text as rich text or inner blocks. ]; } + /** + * {@inheritDoc} + */ + public function validateContent($block = []): array { + $expectedChildren = [ + 'validationType' => GutenbergCardinalityValidatorInterface::CARDINALITY_ANY, + 'min' => 1, + 'max' => GutenbergCardinalityValidatorInterface::CARDINALITY_UNLIMITED, + ]; + return $this->validateCardinality($block, $expectedChildren); + } + } diff --git a/packages/drupal/gutenberg_blocks/src/Plugin/Validation/GutenbergValidator/AccordionValidator.php b/packages/drupal/gutenberg_blocks/src/Plugin/Validation/GutenbergValidator/AccordionValidator.php index 6e7443e53..d0bae38b0 100644 --- a/packages/drupal/gutenberg_blocks/src/Plugin/Validation/GutenbergValidator/AccordionValidator.php +++ b/packages/drupal/gutenberg_blocks/src/Plugin/Validation/GutenbergValidator/AccordionValidator.php @@ -24,6 +24,9 @@ public function applies(array $block): bool { return $block['blockName'] === 'custom/accordion'; } + /** + * {@inheritDoc} + */ public function validateContent($block = []): array { $expectedChildren = [ [ diff --git a/packages/drupal/gutenberg_blocks/src/blocks/accordion-item-text.tsx b/packages/drupal/gutenberg_blocks/src/blocks/accordion-item-text.tsx index 774c0a96c..dd50cd99e 100644 --- a/packages/drupal/gutenberg_blocks/src/blocks/accordion-item-text.tsx +++ b/packages/drupal/gutenberg_blocks/src/blocks/accordion-item-text.tsx @@ -1,5 +1,9 @@ import React, { Fragment } from 'react'; -import { InspectorControls, RichText } from 'wordpress__block-editor'; +import { + InnerBlocks, + InspectorControls, + RichText, +} from 'wordpress__block-editor'; import { registerBlockType } from 'wordpress__blocks'; import { PanelBody, SelectControl } from 'wordpress__components'; import { compose, withState } from 'wordpress__compose'; @@ -16,9 +20,6 @@ registerBlockType('custom/accordion-item-text', { title: { type: 'string', }, - text: { - type: 'string', - }, icon: { type: 'string', }, @@ -67,16 +68,10 @@ registerBlockType('custom/accordion-item-text', { setAttributes({ title: newValue }); }} /> - { - setAttributes({ text: newValue }); - }} +
@@ -84,7 +79,7 @@ registerBlockType('custom/accordion-item-text', { ); }), - save() { - return null; + save: () => { + return ; }, }); diff --git a/packages/schema/src/fragments/PageContent/BlockAccordion.gql b/packages/schema/src/fragments/PageContent/BlockAccordion.gql index d98beaa51..0f77b257d 100644 --- a/packages/schema/src/fragments/PageContent/BlockAccordion.gql +++ b/packages/schema/src/fragments/PageContent/BlockAccordion.gql @@ -7,5 +7,7 @@ fragment BlockAccordion on BlockAccordion { fragment BlockAccordionItemText on BlockAccordionItemText { title icon - text + textContent { + markup + } } diff --git a/packages/schema/src/schema.graphql b/packages/schema/src/schema.graphql index c65d6f320..28a5f02ce 100644 --- a/packages/schema/src/schema.graphql +++ b/packages/schema/src/schema.graphql @@ -253,8 +253,7 @@ type BlockAccordion @type(id: "custom/accordion") { type BlockAccordionItemText @default @value { title: String! @resolveEditorBlockAttribute(key: "title") icon: String! @resolveEditorBlockAttribute(key: "icon") - # @todo check if we want inner blocks here. - text: String! @resolveEditorBlockAttribute(key: "text") + textContent: BlockMarkup @resolveEditorBlockChildren @seek(pos: 0) } type BlockCta @type(id: "custom/cta") { From 6835bb619c4aed89f017f8ca82ab2d79267750b9 Mon Sep 17 00:00:00 2001 From: Christophe Jossart Date: Fri, 3 May 2024 15:44:40 +0200 Subject: [PATCH 037/134] test(SLB-300): accordion block --- .../a397ca48-8fad-411e-8901-0eba2feb989c.yml | 16 +++++++- tests/schema/specs/blocks.spec.ts | 40 +++++++++++++++++-- 2 files changed, 52 insertions(+), 4 deletions(-) diff --git a/packages/drupal/test_content/content/node/a397ca48-8fad-411e-8901-0eba2feb989c.yml b/packages/drupal/test_content/content/node/a397ca48-8fad-411e-8901-0eba2feb989c.yml index ebd66747b..50a8cccb2 100644 --- a/packages/drupal/test_content/content/node/a397ca48-8fad-411e-8901-0eba2feb989c.yml +++ b/packages/drupal/test_content/content/node/a397ca48-8fad-411e-8901-0eba2feb989c.yml @@ -117,9 +117,23 @@ default: + + -

+

Incididunt laborum velit non proident nostrud velit. Minim excepteur ut aliqua nisi. Culpa laboris consectetur proident. Tempor esse ullamco et dolor proident id officia laborum voluptate nostrud elit dolore qui amet. Ex Lorem irure eu anim ipsum officia.

+ + + + + +
  • Moitié-moitié
  • Fribourgeoise
+ + + +

Incididunt laborum velit non proident nostrud velit. Minim excepteur ut aliqua nisi. Culpa laboris consectetur proident. Tempor esse ullamco et dolor proident id officia laborum voluptate nostrud elit dolore qui amet. Ex Lorem irure eu anim ipsum officia.

+ + format: gutenberg summary: '' diff --git a/tests/schema/specs/blocks.spec.ts b/tests/schema/specs/blocks.spec.ts index 4730f8d2a..a7fa91331 100644 --- a/tests/schema/specs/blocks.spec.ts +++ b/tests/schema/specs/blocks.spec.ts @@ -71,6 +71,19 @@ test('Blocks', async () => { __typename } } + ... on BlockAccordion { + items { + __typename + ... on BlockAccordionItemText { + __typename + title + icon + textContent { + markup + } + } + } + } } } { @@ -210,10 +223,31 @@ test('Blocks', async () => { "role": "Project manager", }, { - "__typename": "BlockMarkup", - "markup": " -

+ "__typename": "BlockAccordion", + "items": [ + { + "__typename": "BlockAccordionItemText", + "icon": "", + "textContent": { + "markup": " +

Incididunt laborum velit non proident nostrud velit. Minim excepteur ut aliqua nisi. Culpa laboris consectetur proident. Tempor esse ullamco et dolor proident id officia laborum voluptate nostrud elit dolore qui amet. Ex Lorem irure eu anim ipsum officia.

+ ", + }, + "title": "With a single paragraph and no icon", + }, + { + "__typename": "BlockAccordionItemText", + "icon": "arrow", + "textContent": { + "markup": " +
  • Moitié-moitié
  • Fribourgeoise
+ +

Incididunt laborum velit non proident nostrud velit. Minim excepteur ut aliqua nisi. Culpa laboris consectetur proident. Tempor esse ullamco et dolor proident id officia laborum voluptate nostrud elit dolore qui amet. Ex Lorem irure eu anim ipsum officia.

", + }, + "title": "With a list and a paragraph and arrow icon", + }, + ], }, ], "hero": { From e04e7233f6043586d8706b650f3bfb822464c93e Mon Sep 17 00:00:00 2001 From: Eli Stone Date: Fri, 3 May 2024 14:57:22 +0100 Subject: [PATCH 038/134] feat(slb-306): new gutenberg block for info-grid --- .../gutenberg_blocks/images/icons/email.svg | 1 + .../images/icons/life-ring.svg | 1 + .../gutenberg_blocks/images/icons/phone.svg | 1 + .../src/blocks/info-grid-item.tsx | 55 +++++++++++++++++++ .../gutenberg_blocks/src/blocks/info-grid.tsx | 38 +++++++++++++ packages/drupal/gutenberg_blocks/src/index.ts | 2 + .../gutenberg_blocks/src/utils/icon-list.ts | 25 +++++++++ 7 files changed, 123 insertions(+) create mode 100644 packages/drupal/gutenberg_blocks/images/icons/email.svg create mode 100644 packages/drupal/gutenberg_blocks/images/icons/life-ring.svg create mode 100644 packages/drupal/gutenberg_blocks/images/icons/phone.svg create mode 100644 packages/drupal/gutenberg_blocks/src/blocks/info-grid-item.tsx create mode 100644 packages/drupal/gutenberg_blocks/src/blocks/info-grid.tsx create mode 100644 packages/drupal/gutenberg_blocks/src/utils/icon-list.ts diff --git a/packages/drupal/gutenberg_blocks/images/icons/email.svg b/packages/drupal/gutenberg_blocks/images/icons/email.svg new file mode 100644 index 000000000..e8a2b5593 --- /dev/null +++ b/packages/drupal/gutenberg_blocks/images/icons/email.svg @@ -0,0 +1 @@ + diff --git a/packages/drupal/gutenberg_blocks/images/icons/life-ring.svg b/packages/drupal/gutenberg_blocks/images/icons/life-ring.svg new file mode 100644 index 000000000..304d20a2b --- /dev/null +++ b/packages/drupal/gutenberg_blocks/images/icons/life-ring.svg @@ -0,0 +1 @@ + diff --git a/packages/drupal/gutenberg_blocks/images/icons/phone.svg b/packages/drupal/gutenberg_blocks/images/icons/phone.svg new file mode 100644 index 000000000..db2787d11 --- /dev/null +++ b/packages/drupal/gutenberg_blocks/images/icons/phone.svg @@ -0,0 +1 @@ + diff --git a/packages/drupal/gutenberg_blocks/src/blocks/info-grid-item.tsx b/packages/drupal/gutenberg_blocks/src/blocks/info-grid-item.tsx new file mode 100644 index 000000000..44ae57968 --- /dev/null +++ b/packages/drupal/gutenberg_blocks/src/blocks/info-grid-item.tsx @@ -0,0 +1,55 @@ +import { InnerBlocks, InspectorControls } from "wordpress__block-editor"; +import { registerBlockType } from "wordpress__blocks"; +import { PanelBody, SelectControl } from 'wordpress__components'; +import { iconImagePreview, iconListOptions } from "../utils/icon-list"; + +// @ts-ignore +const { t: __ } = Drupal; + +registerBlockType("custom/info-grid-item", { + title: __("Info Grid Item"), + icon: "align-wide", + category: "layout", + attributes: { + icon: { + type: "string", + default: "EMAIL" + } + }, + edit: (props) => { + const { setAttributes } = props; + const iconPreview = iconImagePreview(props.attributes.icon as string); + const iconPreviewStyle = { maxWidth: '50px' }; + + return ( + <> + + + { + setAttributes({ icon }); + }} + /> + + + +
+
{__("Info Grid Item")}
+
+ {props.attributes.icon +
+ +
+ + ); + }, + save: () => +}); diff --git a/packages/drupal/gutenberg_blocks/src/blocks/info-grid.tsx b/packages/drupal/gutenberg_blocks/src/blocks/info-grid.tsx new file mode 100644 index 000000000..18620fa33 --- /dev/null +++ b/packages/drupal/gutenberg_blocks/src/blocks/info-grid.tsx @@ -0,0 +1,38 @@ +import { InnerBlocks } from "wordpress__block-editor"; +import { registerBlockType } from "wordpress__blocks"; +import { useSelect } from 'wordpress__data'; + + +// @ts-ignore +const { t: __ } = Drupal; + +const MAX_BLOCKS: number = 3; + +registerBlockType("custom/info-grid", { + title: __("Info Grid"), + icon: "editor-insertmore", + category: "layout", + attributes: {}, + edit: (props) => { + const { blockCount } = useSelect((select) => ({ + blockCount: select('core/block-editor').getBlockCount(props.clientId), + })); return ( +
+
{__("Info Grid")}
+ { + if (blockCount >= MAX_BLOCKS) { + return null; + } else { + return ; + } + }} + allowedBlocks={["custom/info-grid-item"]} + template={[]} + /> +
+ ); + }, + save: () => +}); diff --git a/packages/drupal/gutenberg_blocks/src/index.ts b/packages/drupal/gutenberg_blocks/src/index.ts index 2763bdad8..09d0dc458 100644 --- a/packages/drupal/gutenberg_blocks/src/index.ts +++ b/packages/drupal/gutenberg_blocks/src/index.ts @@ -9,3 +9,5 @@ import './blocks/image-with-text'; import './filters/list'; import './blocks/cta'; import './blocks/quote'; +import './blocks/info-grid' +import './blocks/info-grid-item' diff --git a/packages/drupal/gutenberg_blocks/src/utils/icon-list.ts b/packages/drupal/gutenberg_blocks/src/utils/icon-list.ts new file mode 100644 index 000000000..8953de6b0 --- /dev/null +++ b/packages/drupal/gutenberg_blocks/src/utils/icon-list.ts @@ -0,0 +1,25 @@ +// @ts-ignore +const { t: __ } = Drupal; + +const ICON_PATH = "/modules/custom/gutenberg_blocks/images/icons/"; + +// Icon list options +export const iconListOptions: { label: any, value: string }[] = [ + { label: __("Email"), value: "EMAIL" }, + { label: __("Telephone"), value: "PHONE" }, + { label: __("Life Ring"), value: "LIFE-RING" } +]; + +// Icon image preview +export const iconImagePreview = (icon: string) => { + switch (icon) { + case "EMAIL": + return ICON_PATH + "email.svg"; + case "PHONE": + return ICON_PATH + "phone.svg"; + case "LIFE-RING": + return ICON_PATH + "life-ring.svg"; + default: + return ""; + } +} From 18d3aa51cfb0365e95a689a9e3281cacc49eb8d5 Mon Sep 17 00:00:00 2001 From: Christophe Jossart Date: Fri, 3 May 2024 18:03:29 +0200 Subject: [PATCH 039/134] feat(SLB-301): accordion storybook --- packages/schema/src/schema.graphql | 2 +- .../PageContent/BlockAccordion.stories.tsx | 56 +++++++++++++++ .../Organisms/PageContent/BlockAccordion.tsx | 68 +++++++++++++++++++ .../src/components/Organisms/PageDisplay.tsx | 5 +- 4 files changed, 127 insertions(+), 4 deletions(-) create mode 100644 packages/ui/src/components/Organisms/PageContent/BlockAccordion.stories.tsx create mode 100644 packages/ui/src/components/Organisms/PageContent/BlockAccordion.tsx diff --git a/packages/schema/src/schema.graphql b/packages/schema/src/schema.graphql index 28a5f02ce..e41512dcb 100644 --- a/packages/schema/src/schema.graphql +++ b/packages/schema/src/schema.graphql @@ -247,7 +247,7 @@ type BlockImageTeaser @default @value { } type BlockAccordion @type(id: "custom/accordion") { - items: [BlockAccordionItemText]! @resolveEditorBlockChildren + items: [BlockAccordionItemText!]! @resolveEditorBlockChildren } type BlockAccordionItemText @default @value { diff --git a/packages/ui/src/components/Organisms/PageContent/BlockAccordion.stories.tsx b/packages/ui/src/components/Organisms/PageContent/BlockAccordion.stories.tsx new file mode 100644 index 000000000..867eaef1b --- /dev/null +++ b/packages/ui/src/components/Organisms/PageContent/BlockAccordion.stories.tsx @@ -0,0 +1,56 @@ +import { Markup } from '@custom/schema'; +import { Meta, StoryObj } from '@storybook/react'; + +import { BlockAccordion } from './BlockAccordion'; + +export default { + component: BlockAccordion, +} satisfies Meta; + +export const AccordionItemText = { + args: { + items: [ + { + title: 'Cheese Fondue', + icon: '', + textContent: { + markup: ` +

The earliest known recipe for the modern form of cheese fondue comes from a 1699 book published in Zürich, under the name "Käss mit Wein zu kochen" 'to cook cheese with wine'. It calls for grated or cut-up cheese to be melted with wine, and for bread to be dipped in it.

+
    +
  • Fribourgeoise
  • +
  • Moitié-Moitié
  • +
+ ` as Markup, + }, + }, + { + title: 'Rösti', + icon: 'questionmark', + textContent: { + markup: ` +

Rösti is a kind of fried potato cake served as a main course or side dish.

+

As a main dish, rösti is usually accompanied with cheese, onions and cold meat or eggs. This dish, originally from Zürich, was first simply made by frying grated raw potatoes in a pan. It has then spread towards Bern where it is made with boiled potatoes instead. This is where it took the name Rösti.[20] There are many variants in Switzerland and outside the borders.[21] This culinary specialty gives its name to the röstigraben, which designates the cultural differences between the German- and French-speaking parts of the country.

+ ` as Markup, + }, + }, + { + title: 'Älplermagronen', + icon: 'checkmark', + textContent: { + markup: ` +

Älplermagronen are now regarded as a traditional dish of the Swiss Alps and a classic of Swiss comfort foods. According to a popular theory, pasta became widespread in northern Switzerland in the late 19th century, when the Gotthard Tunnel was built, partly by Italian workers who brought dry pasta with them.

+ ` as Markup, + }, + }, + { + title: 'Meringue with double cream', + icon: 'arrow', + textContent: { + markup: ` +

The Oxford English Dictionary states that the French word is of unknown origin. The name meringue for this confection first appeared in print in François Massialot's cookbook of 1692.

+ ` as Markup, + }, + }, + ], + }, +} satisfies StoryObj; diff --git a/packages/ui/src/components/Organisms/PageContent/BlockAccordion.tsx b/packages/ui/src/components/Organisms/PageContent/BlockAccordion.tsx new file mode 100644 index 000000000..4fa234038 --- /dev/null +++ b/packages/ui/src/components/Organisms/PageContent/BlockAccordion.tsx @@ -0,0 +1,68 @@ +import { + BlockAccordionFragment, + BlockAccordionItemTextFragment, +} from '@custom/schema'; +import { + ArrowRightCircleIcon, + CheckCircleIcon, + QuestionMarkCircleIcon, +} from '@heroicons/react/20/solid'; +import React from 'react'; + +import { BlockMarkup } from './BlockMarkup'; + +export function BlockAccordion(props: BlockAccordionFragment) { + return ( +
+ {props.items.map((item, index) => ( + + ))} +
+ ); +} + +function AccordionItemText( + props: BlockAccordionItemTextFragment & { + id: number; + }, +) { + return ( + <> +

+ +

+
+
+ {props.textContent?.markup && } +
+
+ + ); +} + +function AccordionIcon({ icon }: { icon: string }) { + switch (icon) { + case 'questionmark': + return ; + case 'checkmark': + return ; + case 'arrow': + return ; + default: + return null; + } +} diff --git a/packages/ui/src/components/Organisms/PageDisplay.tsx b/packages/ui/src/components/Organisms/PageDisplay.tsx index 6369f68f6..1e01edf5b 100644 --- a/packages/ui/src/components/Organisms/PageDisplay.tsx +++ b/packages/ui/src/components/Organisms/PageDisplay.tsx @@ -4,6 +4,7 @@ import React from 'react'; import { isTruthy } from '../../utils/isTruthy'; import { UnreachableCaseError } from '../../utils/unreachable-case-error'; import { PageTransition } from '../Molecules/PageTransition'; +import { BlockAccordion } from './PageContent/BlockAccordion'; import { BlockCta } from './PageContent/BlockCta'; import { BlockForm } from './PageContent/BlockForm'; import { BlockMarkup } from './PageContent/BlockMarkup'; @@ -61,9 +62,7 @@ export function PageDisplay(page: PageFragment) { case 'BlockQuote': return
; case 'BlockAccordion': - // @todo implement. - // eslint-disable-next-line react/jsx-no-literals - return
BlockAccordion goes here
; + return ; default: throw new UnreachableCaseError(block); } From 46520712f9ab7548a26f47bc772ef8de8d98b847 Mon Sep 17 00:00:00 2001 From: Christophe Jossart Date: Fri, 3 May 2024 19:05:16 +0200 Subject: [PATCH 040/134] refactor(SLB-301): use custom theme --- packages/ui/package.json | 1 + .../Organisms/PageContent/BlockAccordion.tsx | 108 ++++--- pnpm-lock.yaml | 276 +++++++++++++++--- 3 files changed, 293 insertions(+), 92 deletions(-) diff --git a/packages/ui/package.json b/packages/ui/package.json index f3320b5ce..967e148ea 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -44,6 +44,7 @@ "@heroicons/react": "^2.1.1", "@hookform/resolvers": "^3.3.3", "clsx": "^2.1.0", + "flowbite-react": "^0.9.0", "framer-motion": "^10.17.4", "hast-util-is-element": "^2.1.3", "hast-util-select": "^5.0.5", diff --git a/packages/ui/src/components/Organisms/PageContent/BlockAccordion.tsx b/packages/ui/src/components/Organisms/PageContent/BlockAccordion.tsx index 4fa234038..d9838dce7 100644 --- a/packages/ui/src/components/Organisms/PageContent/BlockAccordion.tsx +++ b/packages/ui/src/components/Organisms/PageContent/BlockAccordion.tsx @@ -1,67 +1,85 @@ -import { - BlockAccordionFragment, - BlockAccordionItemTextFragment, -} from '@custom/schema'; +import { BlockAccordionFragment } from '@custom/schema'; import { ArrowRightCircleIcon, CheckCircleIcon, QuestionMarkCircleIcon, } from '@heroicons/react/20/solid'; +import { Accordion, CustomFlowbiteTheme, Flowbite } from 'flowbite-react'; import React from 'react'; import { BlockMarkup } from './BlockMarkup'; -export function BlockAccordion(props: BlockAccordionFragment) { - return ( -
- {props.items.map((item, index) => ( - - ))} -
- ); -} - -function AccordionItemText( - props: BlockAccordionItemTextFragment & { - id: number; +const accordionTheme: CustomFlowbiteTheme['accordion'] = { + root: { + base: 'divide-y divide-gray-200 border-gray-200 dark:divide-gray-700 dark:border-gray-700', + flush: { + off: 'border-b', + on: 'border-b', + }, }, -) { + content: { + base: 'p-2 m-0 text-gray-200 dark:bg-gray-900', + }, + title: { + arrow: { + base: 'h-0 w-0', + }, + base: 'flex w-full items-center justify-between p-5 text-left font-medium text-gray-500 dark:text-gray-400', + flush: { + off: 'hover:bg-gray-100 dark:hover:bg-gray-800 dark:focus:ring-gray-800', + on: 'bg-transparent dark:bg-transparent', + }, + heading: '', + open: { + off: '', + on: 'text-gray-900 dark:text-gray-100', + }, + }, +}; + +// Applying the custom theme to the Accordion component +// doesn't work out, wrapping it in a Flowbite component. +const theme: CustomFlowbiteTheme = { + accordion: accordionTheme, +}; + +export function BlockAccordion(props: BlockAccordionFragment) { return ( - <> -

- -

-
-
- {props.textContent?.markup && } -
-
- + + + {props.items.map((item, index) => ( + + + + {item.icon && } {item.title} + + + + {item.textContent?.markup && ( + + )} + + + ))} + + ); } function AccordionIcon({ icon }: { icon: string }) { switch (icon) { case 'questionmark': - return ; + return ( + + ); case 'checkmark': - return ; + return ( + + ); case 'arrow': - return ; + return ( + + ); default: return null; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 05006d49f..2d882578c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -16,7 +16,7 @@ importers: devDependencies: '@commitlint/cli': specifier: ^18.4.3 - version: 18.4.3(@types/node@18.19.31)(typescript@5.3.3) + version: 18.4.3(@types/node@20.11.17)(typescript@5.3.3) '@commitlint/config-conventional': specifier: ^18.4.3 version: 18.4.3 @@ -64,7 +64,7 @@ importers: version: 5.3.3 vitest: specifier: ^1.1.1 - version: 1.1.1(@types/node@18.19.31) + version: 1.1.1(@types/node@20.11.17) apps/cms: dependencies: @@ -532,6 +532,9 @@ importers: clsx: specifier: ^2.1.0 version: 2.1.0 + flowbite-react: + specifier: ^0.9.0 + version: 0.9.0(react-dom@18.2.0)(react@18.2.0)(tailwindcss@3.4.0) framer-motion: specifier: ^10.17.4 version: 10.17.4(react-dom@18.2.0)(react@18.2.0) @@ -601,7 +604,7 @@ importers: version: 8.0.0-alpha.14(jest@29.7.0)(vitest@1.1.1) '@storybook/test-runner': specifier: ^0.16.0 - version: 0.16.0(@types/node@18.19.31) + version: 0.16.0(@types/node@20.11.17) '@swc/cli': specifier: ^0.1.63 version: 0.1.63(@swc/core@1.3.102) @@ -694,7 +697,7 @@ importers: version: 5.3.3 vite: specifier: ^5.0.10 - version: 5.0.10(@types/node@18.19.31) + version: 5.0.10(@types/node@20.11.17) vite-imagetools: specifier: ^6.2.9 version: 6.2.9 @@ -703,7 +706,7 @@ importers: version: 1.0.3 vitest: specifier: ^1.1.1 - version: 1.1.1(@types/node@18.19.31)(happy-dom@12.10.3) + version: 1.1.1(@types/node@20.11.17)(happy-dom@12.10.3) tests/e2e: devDependencies: @@ -2687,14 +2690,14 @@ packages: resolution: {integrity: sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==} engines: {node: '>=0.1.90'} - /@commitlint/cli@18.4.3(@types/node@18.19.31)(typescript@5.3.3): + /@commitlint/cli@18.4.3(@types/node@20.11.17)(typescript@5.3.3): resolution: {integrity: sha512-zop98yfB3A6NveYAZ3P1Mb6bIXuCeWgnUfVNkH4yhIMQpQfzFwseadazOuSn0OOfTt0lWuFauehpm9GcqM5lww==} engines: {node: '>=v18'} hasBin: true dependencies: '@commitlint/format': 18.6.1 '@commitlint/lint': 18.6.1 - '@commitlint/load': 18.6.1(@types/node@18.19.31)(typescript@5.3.3) + '@commitlint/load': 18.6.1(@types/node@20.11.17)(typescript@5.3.3) '@commitlint/read': 18.6.1 '@commitlint/types': 18.6.1 execa: 5.1.1 @@ -2765,7 +2768,7 @@ packages: '@commitlint/types': 18.6.1 dev: true - /@commitlint/load@18.6.1(@types/node@18.19.31)(typescript@5.3.3): + /@commitlint/load@18.6.1(@types/node@20.11.17)(typescript@5.3.3): resolution: {integrity: sha512-p26x8734tSXUHoAw0ERIiHyW4RaI4Bj99D8YgUlVV9SedLf8hlWAfyIFhHRIhfPngLlCe0QYOdRKYFt8gy56TA==} engines: {node: '>=v18'} dependencies: @@ -2775,7 +2778,7 @@ packages: '@commitlint/types': 18.6.1 chalk: 4.1.2 cosmiconfig: 8.3.6(typescript@5.3.3) - cosmiconfig-typescript-loader: 5.0.0(@types/node@18.19.31)(cosmiconfig@8.3.6)(typescript@5.3.3) + cosmiconfig-typescript-loader: 5.0.0(@types/node@20.11.17)(cosmiconfig@8.3.6)(typescript@5.3.3) lodash.isplainobject: 4.0.6 lodash.merge: 4.6.2 lodash.uniq: 4.5.0 @@ -4158,14 +4161,12 @@ packages: resolution: {integrity: sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g==} dependencies: '@floating-ui/utils': 0.2.1 - dev: true /@floating-ui/dom@1.6.3: resolution: {integrity: sha512-RnDthu3mzPlQ31Ss/BTwQ1zjzIhr3lk1gZB1OC56h/1vEtaXkESrOqL5fQVMfXpwGtRwX+YsZBdyHtJMQnkArw==} dependencies: '@floating-ui/core': 1.6.0 '@floating-ui/utils': 0.2.1 - dev: true /@floating-ui/react-dom@2.0.8(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-HOdqOt3R3OGeTKidaLvJKcgg75S6tibQ3Tif4eyd91QnIJWr0NLvoXFpJA/j8HqkFSL68GDca9AuyWEHlhyClw==} @@ -4176,11 +4177,22 @@ packages: '@floating-ui/dom': 1.6.3 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - dev: true + + /@floating-ui/react@0.26.10(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-sh6f9gVvWQdEzLObrWbJ97c0clJObiALsFe0LiR/kb3tDRKwEhObASEH2QyfdoO/ZBPzwxa9j+nYFo+sqgbioA==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + dependencies: + '@floating-ui/react-dom': 2.0.8(react-dom@18.2.0)(react@18.2.0) + '@floating-ui/utils': 0.2.1 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + tabbable: 6.2.0 + dev: false /@floating-ui/utils@0.2.1: resolution: {integrity: sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==} - dev: true /@formatjs/cli@6.2.4: resolution: {integrity: sha512-g1o9O143F5TGB55skib3fKbyjifPa9YoDcX9L07hVJocRKngCcu4JhKViyUSN55KGcN2ttfBomM+wihN6wtBSQ==} @@ -5683,7 +5695,7 @@ packages: magic-string: 0.27.0 react-docgen-typescript: 2.2.2(typescript@5.3.3) typescript: 5.3.3 - vite: 5.0.10(@types/node@18.19.31) + vite: 5.0.10(@types/node@20.11.17) dev: true /@jridgewell/gen-mapping@0.3.5: @@ -7273,6 +7285,10 @@ packages: resolution: {integrity: sha512-j7P6Rgr3mmtdkeDGTe0E/aYyWEWVtc5yFXtHCRHs28/jptDEWfaVOc5T7cblqy1XKPPfCxJc/8DwQ5YgLOZOVQ==} dev: true + /@popperjs/core@2.11.8: + resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} + dev: false + /@radix-ui/number@1.0.1: resolution: {integrity: sha512-T5gIdVO2mmPW3NNhjNgEP3cqMXjXL9UbO0BzWcXfvdBs+BohbQxvd/K5hSVKmn9/lbTdsQVKbUcP5WLCwvUbBg==} dependencies: @@ -8352,7 +8368,7 @@ packages: magic-string: 0.30.9 rollup: 3.29.4 typescript: 5.3.3 - vite: 5.0.10(@types/node@18.19.31) + vite: 5.0.10(@types/node@20.11.17) transitivePeerDependencies: - encoding - supports-color @@ -8861,7 +8877,7 @@ packages: react: 18.2.0 react-docgen: 7.0.3 react-dom: 18.2.0(react@18.2.0) - vite: 5.0.10(@types/node@18.19.31) + vite: 5.0.10(@types/node@20.11.17) transitivePeerDependencies: - '@preact/preset-vite' - encoding @@ -8935,7 +8951,7 @@ packages: - supports-color dev: true - /@storybook/test-runner@0.16.0(@types/node@18.19.31): + /@storybook/test-runner@0.16.0(@types/node@20.11.17): resolution: {integrity: sha512-LDmNbKFoEDW/VS9o6KR8e1r5MnbCc5ZojUfi5yqLdq80gFD7BvilgKgV0lUh/xWHryzoy+Ids5LYgrPJZmU2dQ==} engines: {node: ^16.10.0 || ^18.0.0 || >=20.0.0} hasBin: true @@ -8955,7 +8971,7 @@ packages: commander: 9.5.0 expect-playwright: 0.8.0 glob: 10.3.12 - jest: 29.7.0(@types/node@18.19.31) + jest: 29.7.0(@types/node@20.11.17) jest-circus: 29.7.0 jest-environment-node: 29.7.0 jest-junit: 16.0.0 @@ -9413,10 +9429,10 @@ packages: chalk: 3.0.0 css.escape: 1.5.1 dom-accessibility-api: 0.6.3 - jest: 29.7.0(@types/node@18.19.31) + jest: 29.7.0(@types/node@20.11.17) lodash: 4.17.21 redent: 3.0.0 - vitest: 1.1.1(@types/node@18.19.31)(happy-dom@12.10.3) + vitest: 1.1.1(@types/node@20.11.17)(happy-dom@12.10.3) dev: true /@testing-library/react@14.1.2(react-dom@18.2.0)(react@18.2.0): @@ -11017,7 +11033,7 @@ packages: vite: ^4 || ^5 dependencies: '@swc/core': 1.4.13 - vite: 5.0.10(@types/node@18.0.0) + vite: 5.0.10(@types/node@20.11.17) transitivePeerDependencies: - '@swc/helpers' dev: true @@ -11044,7 +11060,7 @@ packages: '@babel/plugin-transform-react-jsx-source': 7.24.1(@babel/core@7.24.4) magic-string: 0.27.0 react-refresh: 0.14.0 - vite: 5.0.10(@types/node@18.19.31) + vite: 5.0.10(@types/node@20.11.17) transitivePeerDependencies: - supports-color dev: true @@ -13793,6 +13809,10 @@ packages: static-extend: 0.1.2 dev: false + /classnames@2.5.1: + resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==} + dev: false + /clean-deep@3.4.0: resolution: {integrity: sha512-Lo78NV5ItJL/jl+B5w0BycAisaieJGXK1qYi/9m4SjR8zbqmrUtO7Yhro40wEShGmmxs/aJLI/A+jNhdkXK8mw==} engines: {node: '>=4'} @@ -14425,7 +14445,7 @@ packages: object-assign: 4.1.1 vary: 1.1.2 - /cosmiconfig-typescript-loader@5.0.0(@types/node@18.19.31)(cosmiconfig@8.3.6)(typescript@5.3.3): + /cosmiconfig-typescript-loader@5.0.0(@types/node@20.11.17)(cosmiconfig@8.3.6)(typescript@5.3.3): resolution: {integrity: sha512-+8cK7jRAReYkMwMiG+bxhcNKiHJDM6bR9FD/nGBXOWdMLuYawjF5cGrtLilJ+LGd3ZjCXnJjR5DkfWPoIVlqJA==} engines: {node: '>=v16'} peerDependencies: @@ -14433,7 +14453,7 @@ packages: cosmiconfig: '>=8.2' typescript: '>=4' dependencies: - '@types/node': 18.19.31 + '@types/node': 20.11.17 cosmiconfig: 8.3.6(typescript@5.3.3) jiti: 1.21.0 typescript: 5.3.3 @@ -14559,7 +14579,7 @@ packages: dependencies: '@babel/runtime': 7.24.4 - /create-jest@29.7.0(@types/node@18.19.31): + /create-jest@29.7.0(@types/node@20.11.17): resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} hasBin: true @@ -14568,7 +14588,7 @@ packages: chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@18.19.31) + jest-config: 29.7.0(@types/node@20.11.17) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -14981,6 +15001,11 @@ packages: resolution: {integrity: sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==} dev: true + /debounce@2.0.0: + resolution: {integrity: sha512-xRetU6gL1VJbs85Mc4FoEGSjQxzpdxRyFhe3lmWFyy2EzydIcD4xzUvRJMD+NPDfMwKNhxa3PvsIOU32luIWeA==} + engines: {node: '>=18'} + dev: false + /debug@2.6.9: resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} peerDependencies: @@ -18577,6 +18602,32 @@ packages: engines: {node: '>=0.4.0'} dev: true + /flowbite-react@0.9.0(react-dom@18.2.0)(react@18.2.0)(tailwindcss@3.4.0): + resolution: {integrity: sha512-wRGzTPHaEuRSXiAFhdTuksezABE/AjI/iyOOBGZpsFAz/sq7zuorAqjRud9FWgy3TlFPtldl7kL93wNY2nOnKQ==} + peerDependencies: + react: '>=18' + react-dom: '>=18' + tailwindcss: ^3 + dependencies: + '@floating-ui/core': 1.6.0 + '@floating-ui/react': 0.26.10(react-dom@18.2.0)(react@18.2.0) + classnames: 2.5.1 + debounce: 2.0.0 + flowbite: 2.3.0 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + react-icons: 5.0.1(react@18.2.0) + tailwind-merge: 2.2.2 + tailwindcss: 3.4.0 + dev: false + + /flowbite@2.3.0: + resolution: {integrity: sha512-pm3JRo8OIJHGfFYWgaGpPv8E+UdWy0Z3gEAGufw+G/1dusaU/P1zoBLiQpf2/+bYAi+GBQtPVG86KYlV0W+AFQ==} + dependencies: + '@popperjs/core': 2.11.8 + mini-svg-data-uri: 1.4.4 + dev: false + /flush-write-stream@2.0.0: resolution: {integrity: sha512-uXClqPxT4xW0lcdSBheb2ObVU+kuqUk3Jk64EwieirEXZx9XUrVwp/JuBfKAWaM4T5Td/VL7QLDWPXp/MvGm/g==} dependencies: @@ -22480,7 +22531,7 @@ packages: - supports-color dev: true - /jest-cli@29.7.0(@types/node@18.19.31): + /jest-cli@29.7.0(@types/node@20.11.17): resolution: {integrity: sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} hasBin: true @@ -22494,10 +22545,10 @@ packages: '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 chalk: 4.1.2 - create-jest: 29.7.0(@types/node@18.19.31) + create-jest: 29.7.0(@types/node@20.11.17) exit: 0.1.2 import-local: 3.1.0 - jest-config: 29.7.0(@types/node@18.19.31) + jest-config: 29.7.0(@types/node@20.11.17) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.2 @@ -22548,6 +22599,46 @@ packages: - supports-color dev: true + /jest-config@29.7.0(@types/node@20.11.17): + resolution: {integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@types/node': '*' + ts-node: '>=9.0.0' + peerDependenciesMeta: + '@types/node': + optional: true + ts-node: + optional: true + dependencies: + '@babel/core': 7.24.4 + '@jest/test-sequencer': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.11.17 + babel-jest: 29.7.0(@babel/core@7.24.4) + chalk: 4.1.2 + ci-info: 3.9.0 + deepmerge: 4.3.1 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-circus: 29.7.0 + jest-environment-node: 29.7.0 + jest-get-type: 29.6.3 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-runner: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + micromatch: 4.0.5 + parse-json: 5.2.0 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + dev: true + /jest-diff@29.7.0: resolution: {integrity: sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -22686,7 +22777,7 @@ packages: jest-runner: ^29.3.1 dependencies: expect-playwright: 0.8.0 - jest: 29.7.0(@types/node@18.19.31) + jest: 29.7.0(@types/node@20.11.17) jest-circus: 29.7.0 jest-environment-node: 29.7.0 jest-process-manager: 0.4.0 @@ -22897,7 +22988,7 @@ packages: dependencies: ansi-escapes: 6.2.1 chalk: 5.3.0 - jest: 29.7.0(@types/node@18.19.31) + jest: 29.7.0(@types/node@20.11.17) jest-regex-util: 29.6.3 jest-watcher: 29.7.0 slash: 5.1.0 @@ -22945,7 +23036,7 @@ packages: supports-color: 8.1.1 dev: true - /jest@29.7.0(@types/node@18.19.31): + /jest@29.7.0(@types/node@20.11.17): resolution: {integrity: sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} hasBin: true @@ -22958,7 +23049,7 @@ packages: '@jest/core': 29.7.0 '@jest/types': 29.6.3 import-local: 3.1.0 - jest-cli: 29.7.0(@types/node@18.19.31) + jest-cli: 29.7.0(@types/node@20.11.17) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -24667,7 +24758,6 @@ packages: /mini-svg-data-uri@1.4.4: resolution: {integrity: sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==} hasBin: true - dev: true /minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} @@ -25903,6 +25993,7 @@ packages: /p-limit@4.0.0: resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + requiresBuild: true dependencies: yocto-queue: 1.0.0 @@ -26660,7 +26751,6 @@ packages: postcss-value-parser: 4.2.0 read-cache: 1.0.0 resolve: 1.22.8 - dev: true /postcss-import@15.1.0(postcss@8.4.38): resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} @@ -26695,7 +26785,6 @@ packages: dependencies: camelcase-css: 2.0.1 postcss: 8.4.32 - dev: true /postcss-js@4.0.1(postcss@8.4.38): resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==} @@ -26723,7 +26812,6 @@ packages: lilconfig: 3.1.1 postcss: 8.4.32 yaml: 2.3.4 - dev: true /postcss-load-config@4.0.2(postcss@8.4.38): resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==} @@ -26946,7 +27034,6 @@ packages: dependencies: postcss: 8.4.32 postcss-selector-parser: 6.0.16 - dev: true /postcss-nested@6.0.1(postcss@8.4.38): resolution: {integrity: sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==} @@ -27288,7 +27375,6 @@ packages: nanoid: 3.3.7 picocolors: 1.0.0 source-map-js: 1.2.0 - dev: true /postcss@8.4.38: resolution: {integrity: sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==} @@ -28112,6 +28198,14 @@ packages: source-map: 0.7.4 dev: false + /react-icons@5.0.1(react@18.2.0): + resolution: {integrity: sha512-WqLZJ4bLzlhmsvme6iFdgO8gfZP17rfjYEJ2m9RsZjZ+cc4k1hTzknEz63YS1MeT50kVzoa1Nz36f4BEx+Wigw==} + peerDependencies: + react: '*' + dependencies: + react: 18.2.0 + dev: false + /react-immutable-proptypes@2.2.0(immutable@3.8.2): resolution: {integrity: sha512-Vf4gBsePlwdGvSZoLSBfd4HAP93HDauMY4fDjXhreg/vg6F3Fj/MXDNyTbltPC/xZKmZc+cjLu3598DdYK6sgQ==} peerDependencies: @@ -30871,6 +30965,10 @@ packages: resolution: {integrity: sha512-ulAk51I9UVUyJgxlv9M6lFot2WP3e7t8Kz9+IS6D4rVba1tR9kON+Ey69f+1R4Q8cd45Lod6a4IcJIxnzGc/zA==} engines: {node: '>=18'} + /tabbable@6.2.0: + resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==} + dev: false + /table@5.4.6: resolution: {integrity: sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==} engines: {node: '>=6.0.0'} @@ -30904,6 +31002,12 @@ packages: - supports-color dev: false + /tailwind-merge@2.2.2: + resolution: {integrity: sha512-tWANXsnmJzgw6mQ07nE3aCDkCK4QdT3ThPMCzawoYA2Pws7vSTCvz3Vrjg61jVUGfFZPJzxEP+NimbcW+EdaDw==} + dependencies: + '@babel/runtime': 7.24.4 + dev: false + /tailwindcss@3.4.0: resolution: {integrity: sha512-VigzymniH77knD1dryXbyxR+ePHihHociZbXnLZHUyzf2MMs2ZVqlUrZ3FvpXP8pno9JzmILt1sZPD19M3IxtA==} engines: {node: '>=14.0.0'} @@ -30933,7 +31037,6 @@ packages: sucrase: 3.35.0 transitivePeerDependencies: - ts-node - dev: true /tailwindcss@3.4.3: resolution: {integrity: sha512-U7sxQk/n397Bmx4JHbJx/iSOOv5G+II3f1kpLpY2QeUv5DcPdcTsYLlusZfq1NthHS1c1cZoyFmmkex1rzke0A==} @@ -32640,6 +32743,27 @@ packages: - terser dev: true + /vite-node@1.1.1(@types/node@20.11.17): + resolution: {integrity: sha512-2bGE5w4jvym5v8llF6Gu1oBrmImoNSs4WmRVcavnG2me6+8UQntTqLiAMFyiAobp+ZXhj5ZFhI7SmLiFr/jrow==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + dependencies: + cac: 6.7.14 + debug: 4.3.4 + pathe: 1.1.2 + picocolors: 1.0.0 + vite: 5.2.8(@types/node@20.11.17) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - stylus + - sugarss + - supports-color + - terser + dev: true + /vite-plugin-istanbul@3.0.4: resolution: {integrity: sha512-DJy3cq6yOFbsM3gLQf/3zeuaJNJsfBv5dLFdZdv8sUV30xLtZI+66QeYfHUyP/5vBUYyLA+xNUCSG5uHY6w+5g==} dependencies: @@ -32691,7 +32815,7 @@ packages: fsevents: 2.3.3 dev: true - /vite@5.0.10(@types/node@18.19.31): + /vite@5.0.10(@types/node@20.11.17): resolution: {integrity: sha512-2P8J7WWgmc355HUMlFrwofacvr98DAjoE52BfdbwQtyLH06XKwaL/FMnmKM2crF0iX4MpmMKoDlNCB1ok7zHCw==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true @@ -32719,7 +32843,7 @@ packages: terser: optional: true dependencies: - '@types/node': 18.19.31 + '@types/node': 20.11.17 esbuild: 0.19.12 postcss: 8.4.32 rollup: 4.14.1 @@ -33026,7 +33150,7 @@ packages: - terser dev: true - /vitest@1.1.1(@types/node@18.19.31): + /vitest@1.1.1(@types/node@18.19.31)(happy-dom@12.10.3): resolution: {integrity: sha512-Ry2qs4UOu/KjpXVfOCfQkTnwSXYGrqTbBZxw6reIYEFjSy1QUARRg5pxiI5BEXy+kBVntxUYNMlq4Co+2vD3fQ==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true @@ -33062,6 +33186,7 @@ packages: chai: 4.4.1 debug: 4.3.4 execa: 8.0.1 + happy-dom: 12.10.3 local-pkg: 0.5.0 magic-string: 0.30.9 pathe: 1.1.2 @@ -33083,7 +33208,7 @@ packages: - terser dev: true - /vitest@1.1.1(@types/node@18.19.31)(happy-dom@12.10.3): + /vitest@1.1.1(@types/node@20.11.17): resolution: {integrity: sha512-Ry2qs4UOu/KjpXVfOCfQkTnwSXYGrqTbBZxw6reIYEFjSy1QUARRg5pxiI5BEXy+kBVntxUYNMlq4Co+2vD3fQ==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true @@ -33108,7 +33233,64 @@ packages: jsdom: optional: true dependencies: - '@types/node': 18.19.31 + '@types/node': 20.11.17 + '@vitest/expect': 1.1.1 + '@vitest/runner': 1.1.1 + '@vitest/snapshot': 1.1.1 + '@vitest/spy': 1.1.1 + '@vitest/utils': 1.1.1 + acorn-walk: 8.3.2 + cac: 6.7.14 + chai: 4.4.1 + debug: 4.3.4 + execa: 8.0.1 + local-pkg: 0.5.0 + magic-string: 0.30.9 + pathe: 1.1.2 + picocolors: 1.0.0 + std-env: 3.7.0 + strip-literal: 1.3.0 + tinybench: 2.6.0 + tinypool: 0.8.3 + vite: 5.2.8(@types/node@20.11.17) + vite-node: 1.1.1(@types/node@20.11.17) + why-is-node-running: 2.2.2 + transitivePeerDependencies: + - less + - lightningcss + - sass + - stylus + - sugarss + - supports-color + - terser + dev: true + + /vitest@1.1.1(@types/node@20.11.17)(happy-dom@12.10.3): + resolution: {integrity: sha512-Ry2qs4UOu/KjpXVfOCfQkTnwSXYGrqTbBZxw6reIYEFjSy1QUARRg5pxiI5BEXy+kBVntxUYNMlq4Co+2vD3fQ==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/node': ^18.0.0 || >=20.0.0 + '@vitest/browser': ^1.0.0 + '@vitest/ui': ^1.0.0 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + dependencies: + '@types/node': 20.11.17 '@vitest/expect': 1.1.1 '@vitest/runner': 1.1.1 '@vitest/snapshot': 1.1.1 @@ -33128,8 +33310,8 @@ packages: strip-literal: 1.3.0 tinybench: 2.6.0 tinypool: 0.8.3 - vite: 5.2.8(@types/node@18.19.31) - vite-node: 1.1.1(@types/node@18.19.31) + vite: 5.2.8(@types/node@20.11.17) + vite-node: 1.1.1(@types/node@20.11.17) why-is-node-running: 2.2.2 transitivePeerDependencies: - less From 0186299000c585a002cb9f65f5c497d5e4ff5dd5 Mon Sep 17 00:00:00 2001 From: Christophe Jossart Date: Fri, 3 May 2024 20:00:05 +0200 Subject: [PATCH 041/134] refactor(SLB-301): styling --- .../Organisms/PageContent/BlockAccordion.tsx | 43 ++++++++++++++++--- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/packages/ui/src/components/Organisms/PageContent/BlockAccordion.tsx b/packages/ui/src/components/Organisms/PageContent/BlockAccordion.tsx index d9838dce7..da90274c1 100644 --- a/packages/ui/src/components/Organisms/PageContent/BlockAccordion.tsx +++ b/packages/ui/src/components/Organisms/PageContent/BlockAccordion.tsx @@ -1,13 +1,21 @@ -import { BlockAccordionFragment } from '@custom/schema'; +import { BlockAccordionFragment, Html } from '@custom/schema'; import { ArrowRightCircleIcon, CheckCircleIcon, QuestionMarkCircleIcon, } from '@heroicons/react/20/solid'; +import clsx from 'clsx'; import { Accordion, CustomFlowbiteTheme, Flowbite } from 'flowbite-react'; -import React from 'react'; +import type { Element } from 'hast'; +import { selectAll } from 'hast-util-select'; +import React, { PropsWithChildren } from 'react'; +import { Plugin } from 'unified'; -import { BlockMarkup } from './BlockMarkup'; +const unorderedItems: Plugin<[], Element> = () => (tree) => { + selectAll('ul > li', tree).forEach((node) => { + node.properties!.unordered = true; + }); +}; const accordionTheme: CustomFlowbiteTheme['accordion'] = { root: { @@ -18,7 +26,7 @@ const accordionTheme: CustomFlowbiteTheme['accordion'] = { }, }, content: { - base: 'p-2 m-0 text-gray-200 dark:bg-gray-900', + base: 'pb-5 pt-5 text-base font-normal text-gray-500 dark:bg-gray-900 dark:text-gray-100', }, title: { arrow: { @@ -56,7 +64,32 @@ export function BlockAccordion(props: BlockAccordionFragment) { {item.textContent?.markup && ( - + ) => { + return ( +
  • + {children} +
  • + ); + }, + }} + markup={item.textContent.markup} + /> )}
    From faf98ca890dc826058aef89bd0e2e84d881f28db Mon Sep 17 00:00:00 2001 From: Christophe Jossart Date: Fri, 3 May 2024 20:52:51 +0200 Subject: [PATCH 042/134] refactor(SLB-301): styling --- .../ui/src/components/Organisms/PageContent/BlockAccordion.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/src/components/Organisms/PageContent/BlockAccordion.tsx b/packages/ui/src/components/Organisms/PageContent/BlockAccordion.tsx index da90274c1..0d357e500 100644 --- a/packages/ui/src/components/Organisms/PageContent/BlockAccordion.tsx +++ b/packages/ui/src/components/Organisms/PageContent/BlockAccordion.tsx @@ -26,7 +26,7 @@ const accordionTheme: CustomFlowbiteTheme['accordion'] = { }, }, content: { - base: 'pb-5 pt-5 text-base font-normal text-gray-500 dark:bg-gray-900 dark:text-gray-100', + base: 'pb-5 pt-5 text-base font-light text-gray-500 dark:bg-gray-900 dark:text-gray-100', }, title: { arrow: { From c3da87c3c0f1e7a02056a848402379b15cdc0bec Mon Sep 17 00:00:00 2001 From: Christophe Jossart Date: Fri, 3 May 2024 21:13:27 +0200 Subject: [PATCH 043/134] refactor(SLB-301): styling --- .../Organisms/PageContent/BlockAccordion.stories.tsx | 2 +- .../components/Organisms/PageContent/BlockAccordion.tsx | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/ui/src/components/Organisms/PageContent/BlockAccordion.stories.tsx b/packages/ui/src/components/Organisms/PageContent/BlockAccordion.stories.tsx index 867eaef1b..6f7be4e00 100644 --- a/packages/ui/src/components/Organisms/PageContent/BlockAccordion.stories.tsx +++ b/packages/ui/src/components/Organisms/PageContent/BlockAccordion.stories.tsx @@ -29,7 +29,7 @@ export const AccordionItemText = { textContent: { markup: `

    Rösti is a kind of fried potato cake served as a main course or side dish.

    -

    As a main dish, rösti is usually accompanied with cheese, onions and cold meat or eggs. This dish, originally from Zürich, was first simply made by frying grated raw potatoes in a pan. It has then spread towards Bern where it is made with boiled potatoes instead. This is where it took the name Rösti.[20] There are many variants in Switzerland and outside the borders.[21] This culinary specialty gives its name to the röstigraben, which designates the cultural differences between the German- and French-speaking parts of the country.

    +

    As a main dish, rösti is usually accompanied with cheese, onions and cold meat or eggs. This dish, originally from Zürich, was first simply made by frying grated raw potatoes in a pan. It has then spread towards Bern where it is made with boiled potatoes instead. This is where it took the name Rösti. There are many variants in Switzerland and outside the borders. This culinary specialty gives its name to the röstigraben, which designates the cultural differences between the German- and French-speaking parts of the country.

    ` as Markup, }, }, diff --git a/packages/ui/src/components/Organisms/PageContent/BlockAccordion.tsx b/packages/ui/src/components/Organisms/PageContent/BlockAccordion.tsx index 0d357e500..c53495d9a 100644 --- a/packages/ui/src/components/Organisms/PageContent/BlockAccordion.tsx +++ b/packages/ui/src/components/Organisms/PageContent/BlockAccordion.tsx @@ -21,8 +21,8 @@ const accordionTheme: CustomFlowbiteTheme['accordion'] = { root: { base: 'divide-y divide-gray-200 border-gray-200 dark:divide-gray-700 dark:border-gray-700', flush: { - off: 'border-b', - on: 'border-b', + off: 'border-b last:border-0', + on: 'border-b last:border-0', }, }, content: { @@ -32,7 +32,7 @@ const accordionTheme: CustomFlowbiteTheme['accordion'] = { arrow: { base: 'h-0 w-0', }, - base: 'flex w-full items-center justify-between p-5 text-left font-medium text-gray-500 dark:text-gray-400', + base: 'flex w-full items-center justify-between p-4 pl-1 text-left font-normal text-lg text-gray-500 dark:text-gray-400', flush: { off: 'hover:bg-gray-100 dark:hover:bg-gray-800 dark:focus:ring-gray-800', on: 'bg-transparent dark:bg-transparent', @@ -62,7 +62,7 @@ export function BlockAccordion(props: BlockAccordionFragment) { {item.icon && } {item.title} - + {item.textContent?.markup && ( Date: Fri, 3 May 2024 22:11:41 +0200 Subject: [PATCH 044/134] fix(SLB-301): adjust width --- .../Organisms/PageContent/BlockAccordion.tsx | 58 ++++++++++--------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/packages/ui/src/components/Organisms/PageContent/BlockAccordion.tsx b/packages/ui/src/components/Organisms/PageContent/BlockAccordion.tsx index c53495d9a..b1434696f 100644 --- a/packages/ui/src/components/Organisms/PageContent/BlockAccordion.tsx +++ b/packages/ui/src/components/Organisms/PageContent/BlockAccordion.tsx @@ -63,34 +63,36 @@ export function BlockAccordion(props: BlockAccordionFragment) { - {item.textContent?.markup && ( - ) => { - return ( -
  • - {children} -
  • - ); - }, - }} - markup={item.textContent.markup} - /> - )} +
    + {item.textContent?.markup && ( + ) => { + return ( +
  • + {children} +
  • + ); + }, + }} + markup={item.textContent.markup} + /> + )} +
    ))} From bc0ba2ba1f23a41baf07d49fd335a6bd7079b8f2 Mon Sep 17 00:00:00 2001 From: Christophe Jossart Date: Mon, 6 May 2024 10:03:08 +0200 Subject: [PATCH 045/134] feat(SLB-299): adjust accordion style, add to page story --- .../src/components/Organisms/PageContent/BlockAccordion.tsx | 4 ++-- packages/ui/src/components/Routes/Page.stories.tsx | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/components/Organisms/PageContent/BlockAccordion.tsx b/packages/ui/src/components/Organisms/PageContent/BlockAccordion.tsx index b1434696f..5f4e19453 100644 --- a/packages/ui/src/components/Organisms/PageContent/BlockAccordion.tsx +++ b/packages/ui/src/components/Organisms/PageContent/BlockAccordion.tsx @@ -19,7 +19,7 @@ const unorderedItems: Plugin<[], Element> = () => (tree) => { const accordionTheme: CustomFlowbiteTheme['accordion'] = { root: { - base: 'divide-y divide-gray-200 border-gray-200 dark:divide-gray-700 dark:border-gray-700', + base: 'mt-10 divide-y divide-gray-200 border-gray-200 dark:divide-gray-700 dark:border-gray-700', flush: { off: 'border-b last:border-0', on: 'border-b last:border-0', @@ -63,7 +63,7 @@ export function BlockAccordion(props: BlockAccordionFragment) { -
    +
    {item.textContent?.markup && ( ['content'], }, }, From 8e66171689ad5c32e6fd97b212a3ad769b9f8546 Mon Sep 17 00:00:00 2001 From: Eli Stone Date: Tue, 7 May 2024 10:30:29 +0100 Subject: [PATCH 046/134] feat(slb-306): adding info-grid to frontend --- .idea/prettier.xml | 1 + .../src/blocks/info-grid-item.tsx | 36 ++++----- .../gutenberg_blocks/src/blocks/info-grid.tsx | 25 +++--- .../a397ca48-8fad-411e-8901-0eba2feb989c.yml | 81 ++++++++++--------- packages/schema/src/fragments/Page.gql | 1 + .../fragments/PageContent/BlockInfoGrid.gql | 12 +++ packages/schema/src/schema.graphql | 10 +++ .../src/components/Organisms/PageDisplay.tsx | 15 ++++ 8 files changed, 112 insertions(+), 69 deletions(-) create mode 100644 packages/schema/src/fragments/PageContent/BlockInfoGrid.gql diff --git a/.idea/prettier.xml b/.idea/prettier.xml index 6e16fd106..2089b4cf8 100644 --- a/.idea/prettier.xml +++ b/.idea/prettier.xml @@ -1,6 +1,7 @@ +
    -
    {__("Info Grid Item")}
    -
    +
    +
    {__('Info Grid Item')}
    +
    {props.attributes.icon
    ); }, - save: () => + save: () => , }); diff --git a/packages/drupal/gutenberg_blocks/src/blocks/info-grid.tsx b/packages/drupal/gutenberg_blocks/src/blocks/info-grid.tsx index 18620fa33..ba602fb03 100644 --- a/packages/drupal/gutenberg_blocks/src/blocks/info-grid.tsx +++ b/packages/drupal/gutenberg_blocks/src/blocks/info-grid.tsx @@ -1,24 +1,25 @@ -import { InnerBlocks } from "wordpress__block-editor"; -import { registerBlockType } from "wordpress__blocks"; +import { InnerBlocks } from 'wordpress__block-editor'; +import { registerBlockType } from 'wordpress__blocks'; import { useSelect } from 'wordpress__data'; - // @ts-ignore const { t: __ } = Drupal; const MAX_BLOCKS: number = 3; -registerBlockType("custom/info-grid", { - title: __("Info Grid"), - icon: "editor-insertmore", - category: "layout", +registerBlockType('custom/info-grid', { + title: __('Info Grid'), + icon: 'editor-insertmore', + category: 'layout', attributes: {}, edit: (props) => { const { blockCount } = useSelect((select) => ({ blockCount: select('core/block-editor').getBlockCount(props.clientId), - })); return ( -
    -
    {__("Info Grid")}
    + })); + + return ( +
    +
    {__('Info Grid')}
    { @@ -28,11 +29,11 @@ registerBlockType("custom/info-grid", { return ; } }} - allowedBlocks={["custom/info-grid-item"]} + allowedBlocks={['custom/info-grid-item']} template={[]} />
    ); }, - save: () => + save: () => , }); diff --git a/packages/drupal/test_content/content/node/a397ca48-8fad-411e-8901-0eba2feb989c.yml b/packages/drupal/test_content/content/node/a397ca48-8fad-411e-8901-0eba2feb989c.yml index ebd66747b..468f0b586 100644 --- a/packages/drupal/test_content/content/node/a397ca48-8fad-411e-8901-0eba2feb989c.yml +++ b/packages/drupal/test_content/content/node/a397ca48-8fad-411e-8901-0eba2feb989c.yml @@ -8,7 +8,6 @@ _meta: 3a0fe860-a6d6-428a-9474-365bd57509aa: media 478c4289-961d-4ce8-85d6-578ae05f3019: media 72187a1f-3e48-4b45-a9b7-189c6fd7ee26: media - 5dfc1856-e9e4-4f02-9cd6-9d888870ce1a: media default: revision_uid: - @@ -61,32 +60,6 @@ default: - - -

    All kinds of allowed blocks

    - - - -
    • bla
    - - - -

    Heading

    - - - -
    12
    34
    Caption
    - - - -

    Quote

    Citation
    - - - -

    - - -

    Starting from this paragraph, all the following blocks should be aggregated, as they are just HTML

    @@ -103,23 +76,55 @@ default:

    Heading 3

    + +

    Quote

    Citation
    + + - + + + +

    Email us:

    + + + +

    Email us for general queries, including marketing and partnership opportunities.

    + + + +

    hello@company.com

    + + + + + +

    Call us:

    + - + +

    Call us to speak to a member of our team. We are always happy to help.

    + - + +

    +1 (646) 786-5060

    + + - + + +

    Support

    + -

    +

    Check out helpful resources, FAQs and developer tools.

    + + format: gutenberg summary: '' @@ -189,13 +194,13 @@ translations:

    Heading 3 DE

    - - - - - + +

    Quote DE

    Citation DE
    + - + +

    + format: gutenberg summary: '' diff --git a/packages/schema/src/fragments/Page.gql b/packages/schema/src/fragments/Page.gql index cbf2227eb..661d07e59 100644 --- a/packages/schema/src/fragments/Page.gql +++ b/packages/schema/src/fragments/Page.gql @@ -35,6 +35,7 @@ fragment Page on Page { ...BlockCta ...BlockImageWithText ...BlockQuote + ...BlockInfoGrid } metaTags { tag diff --git a/packages/schema/src/fragments/PageContent/BlockInfoGrid.gql b/packages/schema/src/fragments/PageContent/BlockInfoGrid.gql new file mode 100644 index 000000000..4cb06f16e --- /dev/null +++ b/packages/schema/src/fragments/PageContent/BlockInfoGrid.gql @@ -0,0 +1,12 @@ +fragment BlockInfoGrid on BlockInfoGrid { + items { + ...BlockInfoGridItem + } +} + +fragment BlockInfoGridItem on BlockInfoGridItem { + icon + textContent { + markup + } +} diff --git a/packages/schema/src/schema.graphql b/packages/schema/src/schema.graphql index ca52d0ef3..4a9bc9c42 100644 --- a/packages/schema/src/schema.graphql +++ b/packages/schema/src/schema.graphql @@ -206,6 +206,7 @@ union PageContent @resolveEditorBlockType = | BlockCta | BlockImageWithText | BlockQuote + | BlockInfoGrid type BlockForm @type(id: "custom/form") { url: Url @resolveEditorBlockAttribute(key: "formId") @webformIdToUrl(id: "$") @@ -270,6 +271,15 @@ type BlockQuote @type(id: "custom/quote") { image: MediaImage @resolveEditorBlockMedia } +type BlockInfoGrid @type(id: "custom/info-grid") { + items: [BlockInfoGridItem]! @resolveEditorBlockChildren +} + +type BlockInfoGridItem @default @value { + icon: String! @resolveEditorBlockAttribute(key: "icon") + textContent: BlockMarkup @resolveEditorBlockChildren @seek(pos: 0) +} + input PaginationInput { limit: Int! offset: Int! diff --git a/packages/ui/src/components/Organisms/PageDisplay.tsx b/packages/ui/src/components/Organisms/PageDisplay.tsx index 86813de03..483ad0a54 100644 --- a/packages/ui/src/components/Organisms/PageDisplay.tsx +++ b/packages/ui/src/components/Organisms/PageDisplay.tsx @@ -60,6 +60,21 @@ export function PageDisplay(page: PageFragment) { ); case 'BlockQuote': return
    ; + case 'BlockInfoGrid': + return ( + // TODO: Implement BlockImageTeasers +
    + BlockInfoGrid goes here +
    + ); default: throw new UnreachableCaseError(block); } From 7f3b708b32c004bdb2a2079523786f87f7da3228 Mon Sep 17 00:00:00 2001 From: Eli Stone Date: Tue, 7 May 2024 10:30:50 +0100 Subject: [PATCH 047/134] feat(slb-306): added fix command --- packages/drupal/gutenberg_blocks/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/drupal/gutenberg_blocks/package.json b/packages/drupal/gutenberg_blocks/package.json index b31ec99c1..e5a3cc019 100644 --- a/packages/drupal/gutenberg_blocks/package.json +++ b/packages/drupal/gutenberg_blocks/package.json @@ -6,6 +6,7 @@ "dev": "vite build --watch", "prep": "vite build", "test:static": "tsc --noEmit && eslint \"**/*.{ts,tsx,js,jsx}\" --ignore-path=\"./.eslintignore\"", + "test:fix": "tsc --noEmit && eslint \"**/*.{ts,tsx,js,jsx}\" --ignore-path=\"./.eslintignore\" --fix", "gutenberg:generate": "node ./scripts/generate-gutenberg-block.js" }, "dependencies": { From 2ae25de39a1d6142384cdf5b0b1fad17d8d3ba32 Mon Sep 17 00:00:00 2001 From: Vasile Chindris Date: Thu, 18 Apr 2024 11:51:11 +0300 Subject: [PATCH 048/134] feat(SLB-328): added the entity_create_split module --- .../entity_create_split.info.yml | 5 + .../entity_create_split.module | 30 +++ .../entity_create_split.routing.yml | 9 + .../entity_create_split.services.yml | 3 + .../EntityCreateSplitController.php | 35 ++++ .../src/FormOperations.php | 57 ++++++ .../src/FormOperationsInterface.php | 29 +++ .../tests/src/Unit/FormOperationsTest.php | 171 ++++++++++++++++++ 8 files changed, 339 insertions(+) create mode 100644 packages/drupal/entity_create_split/entity_create_split.info.yml create mode 100644 packages/drupal/entity_create_split/entity_create_split.module create mode 100644 packages/drupal/entity_create_split/entity_create_split.routing.yml create mode 100644 packages/drupal/entity_create_split/entity_create_split.services.yml create mode 100644 packages/drupal/entity_create_split/src/Controller/EntityCreateSplitController.php create mode 100644 packages/drupal/entity_create_split/src/FormOperations.php create mode 100644 packages/drupal/entity_create_split/src/FormOperationsInterface.php create mode 100644 packages/drupal/entity_create_split/tests/src/Unit/FormOperationsTest.php diff --git a/packages/drupal/entity_create_split/entity_create_split.info.yml b/packages/drupal/entity_create_split/entity_create_split.info.yml new file mode 100644 index 000000000..b8d2ad45e --- /dev/null +++ b/packages/drupal/entity_create_split/entity_create_split.info.yml @@ -0,0 +1,5 @@ +name: Entity Create Split +type: module +description: 'Provides a route for splitting the entity creation in two steps: first for the mandatory fields and a second one, which is actually the edit form, for the rest of the fields.' +package: Custom +core_version_requirement: ^9 || ^10 diff --git a/packages/drupal/entity_create_split/entity_create_split.module b/packages/drupal/entity_create_split/entity_create_split.module new file mode 100644 index 000000000..b1bb96ec9 --- /dev/null +++ b/packages/drupal/entity_create_split/entity_create_split.module @@ -0,0 +1,30 @@ +getFormObject() instanceof EntityFormInterface) { + $entity = $form_state->getFormObject()->getEntity(); + if (!$entity->isNew() || empty($entity->hideOptionalFormFields)) { + return; + } + /* @var \Drupal\entity_create_split\FormOperationsInterface $formOperations */ + $formOperations = \Drupal::service('entity_create_split.form_operations'); + $formOperations->hideOptionalFields($form); + } +} diff --git a/packages/drupal/entity_create_split/entity_create_split.routing.yml b/packages/drupal/entity_create_split/entity_create_split.routing.yml new file mode 100644 index 000000000..e4890ef8a --- /dev/null +++ b/packages/drupal/entity_create_split/entity_create_split.routing.yml @@ -0,0 +1,9 @@ +entity_create_split.create: + path: '/entity/create/{entity_type}/{bundle}' + defaults: + _controller: '\Drupal\entity_create_split\Controller\EntityCreateSplitController::createForm' + _title: 'Create entity' + requirements: + _permission: 'administer graphql configuration' + options: + _admin_route: TRUE diff --git a/packages/drupal/entity_create_split/entity_create_split.services.yml b/packages/drupal/entity_create_split/entity_create_split.services.yml new file mode 100644 index 000000000..67480d616 --- /dev/null +++ b/packages/drupal/entity_create_split/entity_create_split.services.yml @@ -0,0 +1,3 @@ +services: + entity_create_split.form_operations: + class: Drupal\entity_create_split\FormOperations diff --git a/packages/drupal/entity_create_split/src/Controller/EntityCreateSplitController.php b/packages/drupal/entity_create_split/src/Controller/EntityCreateSplitController.php new file mode 100644 index 000000000..434af1835 --- /dev/null +++ b/packages/drupal/entity_create_split/src/Controller/EntityCreateSplitController.php @@ -0,0 +1,35 @@ +entityTypeManager()->hasDefinition($entity_type)) { + throw new NotFoundHttpException(); + } + $entityTypeDefinition = $this->entityTypeManager()->getDefinition($entity_type); + $bundleKey = $entityTypeDefinition->getKey('bundle'); + + // @todo: find a way to check if the bundle value is allowed. + $entity = $this->entityTypeManager()->getStorage($entity_type)->create([$bundleKey => $bundle]); + $entity->disableGutenberg = TRUE; + $entity->hideOptionalFormFields = TRUE; + $editForm = $this->entityTypeManager()->getFormObject($entity_type, 'default')->setEntity($entity); + return \Drupal::formBuilder()->getForm($editForm); + } +} diff --git a/packages/drupal/entity_create_split/src/FormOperations.php b/packages/drupal/entity_create_split/src/FormOperations.php new file mode 100644 index 000000000..454b0a2f8 --- /dev/null +++ b/packages/drupal/entity_create_split/src/FormOperations.php @@ -0,0 +1,57 @@ +hideOptionalFields($elements[$child]); + if ($this->shouldHideField($elements[$child])) { + $elements[$child]['#access'] = FALSE; + } + // Set the $allChildrenHidden flag to false in case this child is allowed + // to be rendered. + if (!isset($elements[$child]['#access']) || $elements[$child]['#access']) { + $allChildrenHidden = FALSE; + } + } + // And let's check now if we should hide the element completely (if all its + // children are hidden). + if ($allChildrenHidden) { + $elements['#access'] = FALSE; + } + } + + /** + * {@inheritDoc} + */ + public function shouldHideField(array $field): bool { + // Right now, we deny the access of all the elements which are not hidden or + // value, and they have the #required flag set to false. + return isset($field['#type']) && + $field['#type'] !== 'hidden' && + $field['#type'] !== 'value' && + isset($field['#required']) && + $field['#required'] === FALSE; + } +} diff --git a/packages/drupal/entity_create_split/src/FormOperationsInterface.php b/packages/drupal/entity_create_split/src/FormOperationsInterface.php new file mode 100644 index 000000000..bd881a65b --- /dev/null +++ b/packages/drupal/entity_create_split/src/FormOperationsInterface.php @@ -0,0 +1,29 @@ +formOperations = new FormOperations(); + } + + /** + * @covers \Drupal\entity_create_split\FormOperations::shouldHideField + * @dataProvider shouldHideFieldProvider + */ + public function testShouldHideField(array $field, bool $expected) { + $this->assertEquals($expected, $this->formOperations->shouldHideField($field)); + } + + public function shouldHideFieldProvider() { + return [ + // 1. No type set on the form element. + [ + [ + '#property1' => 'lorem', + '#property2' => 'ipsum', + 'children' => ['dolor'], + ], + FALSE, + ], + // 2. A hidden field. + [ + [ + '#type' => 'hidden', + '#value' => 'this is a hidden field', + ], + FALSE, + ], + // 3. A value field. + [ + [ + '#type' => 'value', + '#value' => 'this is a value field', + ], + FALSE, + ], + // 3. A required field. + [ + [ + '#type' => 'textfield', + '#title' => 'this is a text field', + '#required' => TRUE, + ], + FALSE, + ], + // 3. An optional field. + [ + [ + '#type' => 'textfield', + '#title' => 'this is a text field', + '#required' => FALSE, + ], + TRUE, + ], + // 3. A field with no required property set (this should not be hidden. + // To hide a field, the required property has to be specified). + [ + [ + '#type' => 'textfield', + '#title' => 'this is a text field', + ], + FALSE, + ], + ]; + } + + /** + * @covers \Drupal\entity_create_split\FormOperations::hideOptionalFields + */ + public function testHideOptionalFields() { + $formStructure = [ + '#some_attribute' => 'some_value', + 'optional' => [ + '#type' => 'textfield', + '#title' => 'optional textfield', + '#required' => FALSE, + ], + 'required' => [ + '#type' => 'textfield', + '#title' => 'required textfield', + '#required' => TRUE, + ], + 'optional_group' => [ + '#title' => 'optional group', + 'first_element' => [ + '#title' => 'First optional element', + 'widget' => [ + '#type' => 'select', + '#required' => FALSE, + ], + ], + 'second_element' => [ + '#title' => 'Second optional element', + 'widget' => [ + '#type' => 'checkboxes', + '#required' => FALSE, + ], + ], + ], + 'required_group' => [ + '#title' => 'required group', + 'first_element' => [ + '#title' => 'First required element', + 'widget' => [ + '#type' => 'textfield', + '#required' => TRUE, + ], + ], + 'second_element' => [ + '#title' => 'Second required element', + 'widget' => [ + '#type' => 'textarea', + '#required' => TRUE, + ], + ], + ], + 'mixed_group' => [ + '#title' => 'mixed group', + 'first_element' => [ + '#title' => 'First mixed element - required', + 'widget' => [ + '#type' => 'radios', + '#required' => TRUE, + ], + ], + 'second_element' => [ + '#title' => 'Second mixed element - optional', + 'widget' => [ + '#type' => 'textarea', + '#required' => FALSE, + ], + ], + ], + ]; + $expectedResult = $formStructure; + $expectedResult['optional']['#access'] = FALSE; + $expectedResult['optional_group']['first_element']['widget']['#access'] = FALSE; + $expectedResult['optional_group']['second_element']['widget']['#access'] = FALSE; + $expectedResult['optional_group']['first_element']['#access'] = FALSE; + $expectedResult['optional_group']['second_element']['#access'] = FALSE; + $expectedResult['optional_group']['#access'] = FALSE; + $expectedResult['mixed_group']['second_element']['widget']['#access'] = FALSE; + $expectedResult['mixed_group']['second_element']['#access'] = FALSE; + $this->formOperations->hideOptionalFields($formStructure); + $this->assertEquals($expectedResult, $formStructure); + } + +} From 8893f61886cb2e761bde3e8aa45be74aa7862299 Mon Sep 17 00:00:00 2001 From: Vasile Chindris Date: Thu, 18 Apr 2024 16:16:57 +0300 Subject: [PATCH 049/134] feat(SLB-328): add readme and access checks on the entity creation route --- packages/drupal/entity_create_split/README.md | 13 +++++++ .../entity_create_split.module | 9 ++--- .../entity_create_split.routing.yml | 4 +- .../EntityCreateSplitController.php | 39 ++++++++++++++++--- 4 files changed, 52 insertions(+), 13 deletions(-) create mode 100644 packages/drupal/entity_create_split/README.md diff --git a/packages/drupal/entity_create_split/README.md b/packages/drupal/entity_create_split/README.md new file mode 100644 index 000000000..4a9c81851 --- /dev/null +++ b/packages/drupal/entity_create_split/README.md @@ -0,0 +1,13 @@ +# Entity Create Split + +A Drupal module which exposes routes to split an entity create form into two parts: +- on the first step, only the required fields are presented. After submitting the form, a entity is already created +- the second step is actually just the entity edit form, containing all the other optional form fields. + +One important thing is that there are no alterations done on the current entity create routes (for example, on /node/add/page). This module just exposes the route _/entity/create/{entity_type}/{entity_bundle}_ and how this route is used is subject to each specific project. + +## Special case for the Gutenberg editor + +The Gutenberg editor does a lot of alterations on the create form. For this reason, it is better that the form alter hook of the gutenberg module to not run at all. This is not easy possible, so right now the easieist approach is to just patch the module. The patch is included in the _patches/gutenberg_ folder. + +The functionality should also work without the patch, but the initial form will not look that nice. diff --git a/packages/drupal/entity_create_split/entity_create_split.module b/packages/drupal/entity_create_split/entity_create_split.module index b1bb96ec9..0058cf93f 100644 --- a/packages/drupal/entity_create_split/entity_create_split.module +++ b/packages/drupal/entity_create_split/entity_create_split.module @@ -9,18 +9,17 @@ use Drupal\Core\Entity\EntityFormInterface; use Drupal\Core\Form\FormStateInterface; -use Drupal\Core\Render\Element; /** * Implements hook_form_alter(). */ function entity_create_split_form_alter(&$form, FormStateInterface $form_state, $form_id) { - // We are only interested in entity create forms, and only when the - // splitRequiredFields flag is set on the entity (which means the form was - // requested from the entity_create_split.create route. + // We are only interested in entity forms, and only when the + // hideOptionalFormFields flag is set on the entity (which means the form was + // requested from the entity_create_split.create route). if ($form_state->getFormObject() instanceof EntityFormInterface) { $entity = $form_state->getFormObject()->getEntity(); - if (!$entity->isNew() || empty($entity->hideOptionalFormFields)) { + if (!$entity->hideOptionalFormFields) { return; } /* @var \Drupal\entity_create_split\FormOperationsInterface $formOperations */ diff --git a/packages/drupal/entity_create_split/entity_create_split.routing.yml b/packages/drupal/entity_create_split/entity_create_split.routing.yml index e4890ef8a..e5762db49 100644 --- a/packages/drupal/entity_create_split/entity_create_split.routing.yml +++ b/packages/drupal/entity_create_split/entity_create_split.routing.yml @@ -2,8 +2,8 @@ entity_create_split.create: path: '/entity/create/{entity_type}/{bundle}' defaults: _controller: '\Drupal\entity_create_split\Controller\EntityCreateSplitController::createForm' - _title: 'Create entity' + _title_callback: '\Drupal\entity_create_split\Controller\EntityCreateSplitController::getTitle' requirements: - _permission: 'administer graphql configuration' + _custom_access: '\Drupal\entity_create_split\Controller\EntityCreateSplitController::access' options: _admin_route: TRUE diff --git a/packages/drupal/entity_create_split/src/Controller/EntityCreateSplitController.php b/packages/drupal/entity_create_split/src/Controller/EntityCreateSplitController.php index 434af1835..fa1b39bd5 100644 --- a/packages/drupal/entity_create_split/src/Controller/EntityCreateSplitController.php +++ b/packages/drupal/entity_create_split/src/Controller/EntityCreateSplitController.php @@ -2,8 +2,8 @@ namespace Drupal\entity_create_split\Controller; +use Drupal\Core\Access\AccessResult; use Drupal\Core\Controller\ControllerBase; -use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; class EntityCreateSplitController extends ControllerBase { @@ -19,17 +19,44 @@ class EntityCreateSplitController extends ControllerBase { * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException */ public function createForm($entity_type, $bundle) { - if (!$this->entityTypeManager()->hasDefinition($entity_type)) { - throw new NotFoundHttpException(); - } $entityTypeDefinition = $this->entityTypeManager()->getDefinition($entity_type); $bundleKey = $entityTypeDefinition->getKey('bundle'); - - // @todo: find a way to check if the bundle value is allowed. $entity = $this->entityTypeManager()->getStorage($entity_type)->create([$bundleKey => $bundle]); $entity->disableGutenberg = TRUE; $entity->hideOptionalFormFields = TRUE; $editForm = $this->entityTypeManager()->getFormObject($entity_type, 'default')->setEntity($entity); return \Drupal::formBuilder()->getForm($editForm); } + + /** + * Title callback for the createForm() route. + */ + public function getTitle($entity_type, $bundle) { + $entityTypeDefinition = $this->entityTypeManager()->getDefinition($entity_type); + $bundleEntityType = $entityTypeDefinition->getBundleEntityType(); + $bundleLabel = $this->entityTypeManager()->getStorage($bundleEntityType)->load($bundle)->label(); + return $this->t("Create %entity_type: %entity_bundle", [ + '%entity_type' => $entityTypeDefinition->getLabel(), + '%entity_bundle' => $bundleLabel, + ]); + } + + /** + * Access callback for the createForm() route. + */ + public function access($entity_type, $bundle) { + if (!$this->entityTypeManager()->hasDefinition($entity_type)) { + return AccessResult::forbidden(); + } + $entityTypeDefinition = $this->entityTypeManager()->getDefinition($entity_type); + $bundleEntityType = $entityTypeDefinition->getBundleEntityType(); + $bundleEntity = $this->entityTypeManager()->getStorage($bundleEntityType)->load($bundle); + if (!$bundleEntity) { + return AccessResult::forbidden(); + } + + return $this->entityTypeManager() + ->getAccessControlHandler($entity_type) + ->createAccess($bundle, NULL, [], TRUE); + } } From 0f83b9a2230e55cdf8fcbdfa2668792722f0848f Mon Sep 17 00:00:00 2001 From: Vasile Chindris Date: Thu, 18 Apr 2024 16:26:07 +0300 Subject: [PATCH 050/134] feat(SLB-328): added the disable gutenberg flag patch --- .../gutenberg/disable_gutenberg_flag.patch | 15 +++++++++++++++ .../gutenberg/disable_gutenberg_flag.patch | 15 +++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 apps/cms/patches/contrib/gutenberg/disable_gutenberg_flag.patch create mode 100644 packages/drupal/entity_create_split/patches/gutenberg/disable_gutenberg_flag.patch diff --git a/apps/cms/patches/contrib/gutenberg/disable_gutenberg_flag.patch b/apps/cms/patches/contrib/gutenberg/disable_gutenberg_flag.patch new file mode 100644 index 000000000..56d6464d6 --- /dev/null +++ b/apps/cms/patches/contrib/gutenberg/disable_gutenberg_flag.patch @@ -0,0 +1,15 @@ +diff --git a/gutenberg.module b/gutenberg.module +index dec6b80..173c424 100644 +--- a/gutenberg.module ++++ b/gutenberg.module +@@ -1042,6 +1042,10 @@ function _gutenberg_is_gutenberg_enabled(EntityInterface $entity = NULL) { + return FALSE; + } + ++ if (isset($entity->disableGutenberg) && $entity->disableGutenberg === TRUE) { ++ return FALSE; ++ } ++ + if ($entity->getEntityTypeId() !== 'node') { + return FALSE; + } diff --git a/packages/drupal/entity_create_split/patches/gutenberg/disable_gutenberg_flag.patch b/packages/drupal/entity_create_split/patches/gutenberg/disable_gutenberg_flag.patch new file mode 100644 index 000000000..56d6464d6 --- /dev/null +++ b/packages/drupal/entity_create_split/patches/gutenberg/disable_gutenberg_flag.patch @@ -0,0 +1,15 @@ +diff --git a/gutenberg.module b/gutenberg.module +index dec6b80..173c424 100644 +--- a/gutenberg.module ++++ b/gutenberg.module +@@ -1042,6 +1042,10 @@ function _gutenberg_is_gutenberg_enabled(EntityInterface $entity = NULL) { + return FALSE; + } + ++ if (isset($entity->disableGutenberg) && $entity->disableGutenberg === TRUE) { ++ return FALSE; ++ } ++ + if ($entity->getEntityTypeId() !== 'node') { + return FALSE; + } From c911cb888f288d8d3077c8a7ee84a679de80d50b Mon Sep 17 00:00:00 2001 From: Vasile Chindris Date: Thu, 18 Apr 2024 16:41:45 +0300 Subject: [PATCH 051/134] feat(SLB-328): apply the gutenberg patch --- apps/cms/composer.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/cms/composer.json b/apps/cms/composer.json index bb6feeb43..11008faf5 100644 --- a/apps/cms/composer.json +++ b/apps/cms/composer.json @@ -99,7 +99,10 @@ "Fix site install": "https://www.drupal.org/files/issues/2023-07-28/3349663-8.patch" }, "amazeelabs/silverback_gatsby": { - "Autosave preview": "./patches/fetch-entity.patch" + "Autosave preview": "./patches/fetch-entity.patch" + }, + "drupal/gutenberg": { + "Disable gutenberg flag": "patches/contrib/gutenberg/disable_gutenberg_flag.patch" } }, "patchLevel": { From 91911565ac9c49c1414ecfc344aa916dfed2f29b Mon Sep 17 00:00:00 2001 From: Vasile Chindris Date: Tue, 7 May 2024 11:59:27 +0300 Subject: [PATCH 052/134] feat(SLB-328): split entity creation based on a form display mode --- ...re.entity_form_display.node.page.split.yml | 103 +++++++++++ .../sync/core.entity_form_mode.node.split.yml | 11 ++ apps/cms/config/sync/core.extension.yml | 1 + packages/drupal/entity_create_split/README.md | 4 +- .../entity_create_split.module | 20 +- .../entity_create_split.services.yml | 10 +- .../EntityCreateSplitController.php | 3 +- .../EntityCreateSplitRequestSubscriber.php | 108 +++++++++++ .../src/FormOperations.php | 57 ------ .../src/FormOperationsInterface.php | 29 --- .../tests/src/Unit/FormOperationsTest.php | 171 ------------------ 11 files changed, 240 insertions(+), 277 deletions(-) create mode 100644 apps/cms/config/sync/core.entity_form_display.node.page.split.yml create mode 100644 apps/cms/config/sync/core.entity_form_mode.node.split.yml create mode 100644 packages/drupal/entity_create_split/src/EventSubscriber/EntityCreateSplitRequestSubscriber.php delete mode 100644 packages/drupal/entity_create_split/src/FormOperations.php delete mode 100644 packages/drupal/entity_create_split/src/FormOperationsInterface.php delete mode 100644 packages/drupal/entity_create_split/tests/src/Unit/FormOperationsTest.php diff --git a/apps/cms/config/sync/core.entity_form_display.node.page.split.yml b/apps/cms/config/sync/core.entity_form_display.node.page.split.yml new file mode 100644 index 000000000..7c4e46ca1 --- /dev/null +++ b/apps/cms/config/sync/core.entity_form_display.node.page.split.yml @@ -0,0 +1,103 @@ +uuid: a39d0011-2ebf-4b18-a9f0-8b5aeff5b9aa +langcode: en +status: true +dependencies: + config: + - core.entity_form_mode.node.split + - field.field.node.page.body + - field.field.node.page.field_metatags + - node.type.page + - workflows.workflow.basic + module: + - content_moderation + - metatag + - path +id: node.page.split +targetEntityType: node +bundle: page +mode: split +content: + created: + type: datetime_timestamp + weight: 3 + region: content + settings: { } + third_party_settings: { } + field_metatags: + type: metatag_firehose + weight: 11 + region: content + settings: + sidebar: true + use_details: true + third_party_settings: { } + langcode: + type: language_select + weight: 1 + region: content + settings: + include_locked: true + third_party_settings: { } + moderation_state: + type: moderation_state_default + weight: 9 + region: content + settings: { } + third_party_settings: { } + path: + type: path + weight: 7 + region: content + settings: { } + third_party_settings: { } + promote: + type: boolean_checkbox + weight: 5 + region: content + settings: + display_label: true + third_party_settings: { } + status: + type: boolean_checkbox + weight: 10 + region: content + settings: + display_label: true + third_party_settings: { } + sticky: + type: boolean_checkbox + weight: 6 + region: content + settings: + display_label: true + third_party_settings: { } + title: + type: string_textfield + weight: 0 + region: content + settings: + size: 60 + placeholder: '' + third_party_settings: { } + translation: + weight: 4 + region: content + settings: { } + third_party_settings: { } + uid: + type: entity_reference_autocomplete + weight: 2 + region: content + settings: + match_operator: CONTAINS + match_limit: 10 + size: 60 + placeholder: '' + third_party_settings: { } + url_redirects: + weight: 8 + region: content + settings: { } + third_party_settings: { } +hidden: + body: true diff --git a/apps/cms/config/sync/core.entity_form_mode.node.split.yml b/apps/cms/config/sync/core.entity_form_mode.node.split.yml new file mode 100644 index 000000000..8e665c318 --- /dev/null +++ b/apps/cms/config/sync/core.entity_form_mode.node.split.yml @@ -0,0 +1,11 @@ +uuid: 5f26940d-fd06-41e0-8e59-9dfcdc9c86df +langcode: en +status: true +dependencies: + module: + - node +id: node.split +label: Split +description: '' +targetEntityType: node +cache: true diff --git a/apps/cms/config/sync/core.extension.yml b/apps/cms/config/sync/core.extension.yml index c32cf22ef..84b5d6503 100644 --- a/apps/cms/config/sync/core.extension.yml +++ b/apps/cms/config/sync/core.extension.yml @@ -22,6 +22,7 @@ module: dropzonejs: 0 dynamic_page_cache: 0 editor: 0 + entity_create_split: 0 entity_usage: 0 environment_indicator: 0 field: 0 diff --git a/packages/drupal/entity_create_split/README.md b/packages/drupal/entity_create_split/README.md index 4a9c81851..51d58ef2c 100644 --- a/packages/drupal/entity_create_split/README.md +++ b/packages/drupal/entity_create_split/README.md @@ -4,10 +4,10 @@ A Drupal module which exposes routes to split an entity create form into two par - on the first step, only the required fields are presented. After submitting the form, a entity is already created - the second step is actually just the entity edit form, containing all the other optional form fields. -One important thing is that there are no alterations done on the current entity create routes (for example, on /node/add/page). This module just exposes the route _/entity/create/{entity_type}/{entity_bundle}_ and how this route is used is subject to each specific project. +To enable this feature, you must create a form mode with the machine name "split" and enable it on the bundle for which you want to have this feature. ## Special case for the Gutenberg editor -The Gutenberg editor does a lot of alterations on the create form. For this reason, it is better that the form alter hook of the gutenberg module to not run at all. This is not easy possible, so right now the easieist approach is to just patch the module. The patch is included in the _patches/gutenberg_ folder. +The Gutenberg editor does a lot of alterations on the create form. For this reason, it is better that the form alter hook of the gutenberg module to not run at all. This is not easy possible, so right now the easiest approach is to just patch the module. The patch is included in the _patches/gutenberg_ folder. The functionality should also work without the patch, but the initial form will not look that nice. diff --git a/packages/drupal/entity_create_split/entity_create_split.module b/packages/drupal/entity_create_split/entity_create_split.module index 0058cf93f..8b87b1145 100644 --- a/packages/drupal/entity_create_split/entity_create_split.module +++ b/packages/drupal/entity_create_split/entity_create_split.module @@ -7,23 +7,15 @@ * the rest of the fields. */ -use Drupal\Core\Entity\EntityFormInterface; -use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Entity\ContentEntityType; /** - * Implements hook_form_alter(). + * Implements hook_entity_type_build(). */ -function entity_create_split_form_alter(&$form, FormStateInterface $form_state, $form_id) { - // We are only interested in entity forms, and only when the - // hideOptionalFormFields flag is set on the entity (which means the form was - // requested from the entity_create_split.create route). - if ($form_state->getFormObject() instanceof EntityFormInterface) { - $entity = $form_state->getFormObject()->getEntity(); - if (!$entity->hideOptionalFormFields) { - return; +function entity_create_split_entity_type_build(array &$entity_types) { + foreach ($entity_types as $entity_type) { + if ($entity_type instanceof ContentEntityType) { + $entity_type->setFormClass('split', $entity_type->getFormClass('default')); } - /* @var \Drupal\entity_create_split\FormOperationsInterface $formOperations */ - $formOperations = \Drupal::service('entity_create_split.form_operations'); - $formOperations->hideOptionalFields($form); } } diff --git a/packages/drupal/entity_create_split/entity_create_split.services.yml b/packages/drupal/entity_create_split/entity_create_split.services.yml index 67480d616..4216b1192 100644 --- a/packages/drupal/entity_create_split/entity_create_split.services.yml +++ b/packages/drupal/entity_create_split/entity_create_split.services.yml @@ -1,3 +1,9 @@ services: - entity_create_split.form_operations: - class: Drupal\entity_create_split\FormOperations + entity_create_split.route_subscriber: + class: Drupal\entity_create_split\EventSubscriber\EntityCreateSplitRequestSubscriber + arguments: + - "@current_route_match" + - "@entity_type.manager" + - "@entity_display.repository" + tags: + - { name: event_subscriber } diff --git a/packages/drupal/entity_create_split/src/Controller/EntityCreateSplitController.php b/packages/drupal/entity_create_split/src/Controller/EntityCreateSplitController.php index fa1b39bd5..f27b6b2fa 100644 --- a/packages/drupal/entity_create_split/src/Controller/EntityCreateSplitController.php +++ b/packages/drupal/entity_create_split/src/Controller/EntityCreateSplitController.php @@ -23,8 +23,7 @@ public function createForm($entity_type, $bundle) { $bundleKey = $entityTypeDefinition->getKey('bundle'); $entity = $this->entityTypeManager()->getStorage($entity_type)->create([$bundleKey => $bundle]); $entity->disableGutenberg = TRUE; - $entity->hideOptionalFormFields = TRUE; - $editForm = $this->entityTypeManager()->getFormObject($entity_type, 'default')->setEntity($entity); + $editForm = $this->entityTypeManager()->getFormObject($entity_type, 'split')->setEntity($entity); return \Drupal::formBuilder()->getForm($editForm); } diff --git a/packages/drupal/entity_create_split/src/EventSubscriber/EntityCreateSplitRequestSubscriber.php b/packages/drupal/entity_create_split/src/EventSubscriber/EntityCreateSplitRequestSubscriber.php new file mode 100644 index 000000000..10f325d37 --- /dev/null +++ b/packages/drupal/entity_create_split/src/EventSubscriber/EntityCreateSplitRequestSubscriber.php @@ -0,0 +1,108 @@ +routeMatch = $route_match; + $this->entityTypeManager = $entity_type_manager; + $this->entityDisplayRepository = $entity_display_repository; + } + + /** + * {@inheritDoc} + */ + public static function getSubscribedEvents() { + $events[KernelEvents::REQUEST][] = ['onKernelRequest', 0]; + return $events; + } + + public function onKernelRequest(RequestEvent $event) { + $supportedRoutes = $this->supportedRoutes(); + $currentRouteName = $this->routeMatch->getRouteName(); + if (empty($supportedRoutes[$currentRouteName])) { + return; + } + $entityTypeId = $supportedRoutes[$currentRouteName]; + $entityTypeDefinition =$this->entityTypeManager->getDefinition($entityTypeId); + // Make sure the entity has the split form class handler defined. + if (!$entityTypeDefinition->getFormClass('split')) { + return; + } + $bundleEntityType = $entityTypeDefinition->getBundleEntityType(); + $bundle = $this->routeMatch->getParameter($bundleEntityType); + // If, for some reason, we can't load the bundle from the current request, + // then we just stop here. + if (empty($bundle)) { + return; + } + $formModes = $this->entityDisplayRepository->getFormModeOptionsByBundle($entityTypeId, $bundle->id()); + // If the "split" form mode is not setup on the current entity bundle, then + // we also just stop here. + if (empty($formModes['split'])) { + return; + } + + // If we got here, it means that we are on a entity add route, and the + // entity bundle has the split form mode configure, which means we need to + // redirect to the entity_create_split.create route for that entity bundle. + $createSplitUrl = Url::fromRoute('entity_create_split.create', [ + 'entity_type' => $entityTypeId, + 'bundle' => $bundle->id(), + ]); + $response = new TrustedRedirectResponse($createSplitUrl->setAbsolute() + ->toString(), 302); + $event->setResponse($response); + } + + /** + * Helper, temporary, method to define the routes which are checked by the + * event subscriber for redirecting the user to the split form. + * + * @return string[] + */ + protected function supportedRoutes() { + return [ + 'node.add' => 'node', + 'media.add' => 'media', + ]; + } +} diff --git a/packages/drupal/entity_create_split/src/FormOperations.php b/packages/drupal/entity_create_split/src/FormOperations.php deleted file mode 100644 index 454b0a2f8..000000000 --- a/packages/drupal/entity_create_split/src/FormOperations.php +++ /dev/null @@ -1,57 +0,0 @@ -hideOptionalFields($elements[$child]); - if ($this->shouldHideField($elements[$child])) { - $elements[$child]['#access'] = FALSE; - } - // Set the $allChildrenHidden flag to false in case this child is allowed - // to be rendered. - if (!isset($elements[$child]['#access']) || $elements[$child]['#access']) { - $allChildrenHidden = FALSE; - } - } - // And let's check now if we should hide the element completely (if all its - // children are hidden). - if ($allChildrenHidden) { - $elements['#access'] = FALSE; - } - } - - /** - * {@inheritDoc} - */ - public function shouldHideField(array $field): bool { - // Right now, we deny the access of all the elements which are not hidden or - // value, and they have the #required flag set to false. - return isset($field['#type']) && - $field['#type'] !== 'hidden' && - $field['#type'] !== 'value' && - isset($field['#required']) && - $field['#required'] === FALSE; - } -} diff --git a/packages/drupal/entity_create_split/src/FormOperationsInterface.php b/packages/drupal/entity_create_split/src/FormOperationsInterface.php deleted file mode 100644 index bd881a65b..000000000 --- a/packages/drupal/entity_create_split/src/FormOperationsInterface.php +++ /dev/null @@ -1,29 +0,0 @@ -formOperations = new FormOperations(); - } - - /** - * @covers \Drupal\entity_create_split\FormOperations::shouldHideField - * @dataProvider shouldHideFieldProvider - */ - public function testShouldHideField(array $field, bool $expected) { - $this->assertEquals($expected, $this->formOperations->shouldHideField($field)); - } - - public function shouldHideFieldProvider() { - return [ - // 1. No type set on the form element. - [ - [ - '#property1' => 'lorem', - '#property2' => 'ipsum', - 'children' => ['dolor'], - ], - FALSE, - ], - // 2. A hidden field. - [ - [ - '#type' => 'hidden', - '#value' => 'this is a hidden field', - ], - FALSE, - ], - // 3. A value field. - [ - [ - '#type' => 'value', - '#value' => 'this is a value field', - ], - FALSE, - ], - // 3. A required field. - [ - [ - '#type' => 'textfield', - '#title' => 'this is a text field', - '#required' => TRUE, - ], - FALSE, - ], - // 3. An optional field. - [ - [ - '#type' => 'textfield', - '#title' => 'this is a text field', - '#required' => FALSE, - ], - TRUE, - ], - // 3. A field with no required property set (this should not be hidden. - // To hide a field, the required property has to be specified). - [ - [ - '#type' => 'textfield', - '#title' => 'this is a text field', - ], - FALSE, - ], - ]; - } - - /** - * @covers \Drupal\entity_create_split\FormOperations::hideOptionalFields - */ - public function testHideOptionalFields() { - $formStructure = [ - '#some_attribute' => 'some_value', - 'optional' => [ - '#type' => 'textfield', - '#title' => 'optional textfield', - '#required' => FALSE, - ], - 'required' => [ - '#type' => 'textfield', - '#title' => 'required textfield', - '#required' => TRUE, - ], - 'optional_group' => [ - '#title' => 'optional group', - 'first_element' => [ - '#title' => 'First optional element', - 'widget' => [ - '#type' => 'select', - '#required' => FALSE, - ], - ], - 'second_element' => [ - '#title' => 'Second optional element', - 'widget' => [ - '#type' => 'checkboxes', - '#required' => FALSE, - ], - ], - ], - 'required_group' => [ - '#title' => 'required group', - 'first_element' => [ - '#title' => 'First required element', - 'widget' => [ - '#type' => 'textfield', - '#required' => TRUE, - ], - ], - 'second_element' => [ - '#title' => 'Second required element', - 'widget' => [ - '#type' => 'textarea', - '#required' => TRUE, - ], - ], - ], - 'mixed_group' => [ - '#title' => 'mixed group', - 'first_element' => [ - '#title' => 'First mixed element - required', - 'widget' => [ - '#type' => 'radios', - '#required' => TRUE, - ], - ], - 'second_element' => [ - '#title' => 'Second mixed element - optional', - 'widget' => [ - '#type' => 'textarea', - '#required' => FALSE, - ], - ], - ], - ]; - $expectedResult = $formStructure; - $expectedResult['optional']['#access'] = FALSE; - $expectedResult['optional_group']['first_element']['widget']['#access'] = FALSE; - $expectedResult['optional_group']['second_element']['widget']['#access'] = FALSE; - $expectedResult['optional_group']['first_element']['#access'] = FALSE; - $expectedResult['optional_group']['second_element']['#access'] = FALSE; - $expectedResult['optional_group']['#access'] = FALSE; - $expectedResult['mixed_group']['second_element']['widget']['#access'] = FALSE; - $expectedResult['mixed_group']['second_element']['#access'] = FALSE; - $this->formOperations->hideOptionalFields($formStructure); - $this->assertEquals($expectedResult, $formStructure); - } - -} From 9d6e6e103c5a29197053cdb02ce3a14300296e89 Mon Sep 17 00:00:00 2001 From: Vasile Chindris Date: Tue, 7 May 2024 14:32:19 +0300 Subject: [PATCH 053/134] chore(SLB-328): move the gutenberg module patch to contrib --- apps/cms/composer.json | 2 +- apps/cms/patches/.gitkeep | 0 .../gutenberg/disable_gutenberg_flag.patch | 15 --------------- packages/drupal/entity_create_split/README.md | 2 +- .../entity_create_split.module | 10 ++++++++++ 5 files changed, 12 insertions(+), 17 deletions(-) delete mode 100644 apps/cms/patches/.gitkeep delete mode 100644 apps/cms/patches/contrib/gutenberg/disable_gutenberg_flag.patch diff --git a/apps/cms/composer.json b/apps/cms/composer.json index 11008faf5..cd46fb033 100644 --- a/apps/cms/composer.json +++ b/apps/cms/composer.json @@ -102,7 +102,7 @@ "Autosave preview": "./patches/fetch-entity.patch" }, "drupal/gutenberg": { - "Disable gutenberg flag": "patches/contrib/gutenberg/disable_gutenberg_flag.patch" + "Gutenberg enabled hook": "https://www.drupal.org/files/issues/2024-05-07/gutenberg_enabled_hook_3445677-2.patch" } }, "patchLevel": { diff --git a/apps/cms/patches/.gitkeep b/apps/cms/patches/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/apps/cms/patches/contrib/gutenberg/disable_gutenberg_flag.patch b/apps/cms/patches/contrib/gutenberg/disable_gutenberg_flag.patch deleted file mode 100644 index 56d6464d6..000000000 --- a/apps/cms/patches/contrib/gutenberg/disable_gutenberg_flag.patch +++ /dev/null @@ -1,15 +0,0 @@ -diff --git a/gutenberg.module b/gutenberg.module -index dec6b80..173c424 100644 ---- a/gutenberg.module -+++ b/gutenberg.module -@@ -1042,6 +1042,10 @@ function _gutenberg_is_gutenberg_enabled(EntityInterface $entity = NULL) { - return FALSE; - } - -+ if (isset($entity->disableGutenberg) && $entity->disableGutenberg === TRUE) { -+ return FALSE; -+ } -+ - if ($entity->getEntityTypeId() !== 'node') { - return FALSE; - } diff --git a/packages/drupal/entity_create_split/README.md b/packages/drupal/entity_create_split/README.md index 51d58ef2c..12a79a2d2 100644 --- a/packages/drupal/entity_create_split/README.md +++ b/packages/drupal/entity_create_split/README.md @@ -8,6 +8,6 @@ To enable this feature, you must create a form mode with the machine name "split ## Special case for the Gutenberg editor -The Gutenberg editor does a lot of alterations on the create form. For this reason, it is better that the form alter hook of the gutenberg module to not run at all. This is not easy possible, so right now the easiest approach is to just patch the module. The patch is included in the _patches/gutenberg_ folder. +The Gutenberg editor does a lot of alterations on the create form. For this reason, it is better that the form alter hook of the gutenberg module to not run at all. This is not easy possible, so right now the easiest approach is to just patch the module with the patch from https://www.drupal.org/project/gutenberg/issues/3445677/ The functionality should also work without the patch, but the initial form will not look that nice. diff --git a/packages/drupal/entity_create_split/entity_create_split.module b/packages/drupal/entity_create_split/entity_create_split.module index 8b87b1145..2ae2e596f 100644 --- a/packages/drupal/entity_create_split/entity_create_split.module +++ b/packages/drupal/entity_create_split/entity_create_split.module @@ -8,6 +8,7 @@ */ use Drupal\Core\Entity\ContentEntityType; +use Drupal\Core\Entity\EntityInterface; /** * Implements hook_entity_type_build(). @@ -19,3 +20,12 @@ function entity_create_split_entity_type_build(array &$entity_types) { } } } + +/** + * Implements hook_gutenberg_enabled(). + */ +function entity_create_split_gutenberg_enabled(EntityInterface $entity) { + if (isset($entity->disableGutenberg) && $entity->disableGutenberg === TRUE) { + return FALSE; + } +} From c36ae8fea659d6191bec7b6ba716acab76963101 Mon Sep 17 00:00:00 2001 From: Vasile Chindris Date: Tue, 7 May 2024 14:37:26 +0300 Subject: [PATCH 054/134] chore(SLB-328): remove the leftover patch from the module --- .../gutenberg/disable_gutenberg_flag.patch | 15 --------------- 1 file changed, 15 deletions(-) delete mode 100644 packages/drupal/entity_create_split/patches/gutenberg/disable_gutenberg_flag.patch diff --git a/packages/drupal/entity_create_split/patches/gutenberg/disable_gutenberg_flag.patch b/packages/drupal/entity_create_split/patches/gutenberg/disable_gutenberg_flag.patch deleted file mode 100644 index 56d6464d6..000000000 --- a/packages/drupal/entity_create_split/patches/gutenberg/disable_gutenberg_flag.patch +++ /dev/null @@ -1,15 +0,0 @@ -diff --git a/gutenberg.module b/gutenberg.module -index dec6b80..173c424 100644 ---- a/gutenberg.module -+++ b/gutenberg.module -@@ -1042,6 +1042,10 @@ function _gutenberg_is_gutenberg_enabled(EntityInterface $entity = NULL) { - return FALSE; - } - -+ if (isset($entity->disableGutenberg) && $entity->disableGutenberg === TRUE) { -+ return FALSE; -+ } -+ - if ($entity->getEntityTypeId() !== 'node') { - return FALSE; - } From cd04cfa2be368f4aa515a6c130bf3fe30ccfc08b Mon Sep 17 00:00:00 2001 From: Vasile Chindris Date: Tue, 7 May 2024 14:43:31 +0300 Subject: [PATCH 055/134] fix(SLB-328): prettier fix --- apps/cms/composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/cms/composer.json b/apps/cms/composer.json index cd46fb033..50427ad02 100644 --- a/apps/cms/composer.json +++ b/apps/cms/composer.json @@ -99,7 +99,7 @@ "Fix site install": "https://www.drupal.org/files/issues/2023-07-28/3349663-8.patch" }, "amazeelabs/silverback_gatsby": { - "Autosave preview": "./patches/fetch-entity.patch" + "Autosave preview": "./patches/fetch-entity.patch" }, "drupal/gutenberg": { "Gutenberg enabled hook": "https://www.drupal.org/files/issues/2024-05-07/gutenberg_enabled_hook_3445677-2.patch" From 873815aa57817cd069ac719821097dae0a3c96db Mon Sep 17 00:00:00 2001 From: Vasile Chindris Date: Tue, 7 May 2024 15:43:57 +0300 Subject: [PATCH 056/134] test(SLB-328): update e2e tests --- tests/e2e/specs/drupal/content-editing.spec.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/e2e/specs/drupal/content-editing.spec.ts b/tests/e2e/specs/drupal/content-editing.spec.ts index 463d49dc7..b90810480 100644 --- a/tests/e2e/specs/drupal/content-editing.spec.ts +++ b/tests/e2e/specs/drupal/content-editing.spec.ts @@ -6,19 +6,21 @@ test.describe('content-editing', () => { test.use({ storageState: '.auth/admin.json' }); test('moderation controls are placed in the sidebar', async ({ page }) => { - await page.goto(cmsUrl('/node/add/page')); + await page.goto(cmsUrl('/drupal')); + await page.locator('li.tabs__tab a:text("Edit")').click(); await expect( - page.locator('[aria-label="Editor settings"]').getByText('Save as'), + page.locator('[aria-label="Editor settings"]').getByText('Change to'), ).toBeVisible(); }); test('"More settings" fieldset is removed', async ({ page }) => { + await page.goto(cmsUrl('/drupal')); + await page.locator('li.tabs__tab a:text("Edit")').click(); // Why we expect it to be removed: // - It's too long to scroll to the bottom of long pages // - If we have any valuable controls in the "More settings" fieldset, // we should move them to the sidebar, where they are much easier to // access - await page.goto(cmsUrl('/node/add/page')); await expect(page.locator(':text-is("More settings")')).toHaveCount(0); }); }); From 920222c53a6bb2875dec23c1bce89a18d362c588 Mon Sep 17 00:00:00 2001 From: Vasile Chindris Date: Tue, 7 May 2024 15:57:09 +0300 Subject: [PATCH 057/134] test(SLB-328): update preview tests --- tests/e2e/specs/drupal/preview.spec.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/e2e/specs/drupal/preview.spec.ts b/tests/e2e/specs/drupal/preview.spec.ts index 78865a978..4f4f0d880 100644 --- a/tests/e2e/specs/drupal/preview.spec.ts +++ b/tests/e2e/specs/drupal/preview.spec.ts @@ -10,6 +10,8 @@ test.describe('instant preview', () => { await page .getByLabel('Title', { exact: true }) .fill('Instant preview test'); + await page.locator('#edit-submit').click(); + await page.locator('li.tabs__tab a:text("Edit")').click(); await page .locator('#editor-edit-body-0-value h1 span') .first() From e308440943354c51fa5f3b775de589a6b9f41430 Mon Sep 17 00:00:00 2001 From: Luqmaan Essop Date: Fri, 3 May 2024 07:24:05 +0200 Subject: [PATCH 058/134] chore: fix gitpod configuration --- .gitpod.Dockerfile | 5 ----- .gitpod.yml | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/.gitpod.Dockerfile b/.gitpod.Dockerfile index 913f8189c..7061f94f0 100644 --- a/.gitpod.Dockerfile +++ b/.gitpod.Dockerfile @@ -28,8 +28,3 @@ RUN chmod a+x phpactor.phar RUN sudo mv phpactor.phar /usr/local/bin/phpactor # Install gh cli RUN sudo install-packages gh - -RUN curl -Lo lazygit.tar.gz "https://github.com/jesseduffield/lazygit/releases/latest/download/lazygit_0.40.2_Linux_x86_64.tar.gz" \ - && tar xf lazygit.tar.gz lazygit \ - && sudo install lazygit /usr/local/bin - diff --git a/.gitpod.yml b/.gitpod.yml index b8f8aab49..ccd012215 100644 --- a/.gitpod.yml +++ b/.gitpod.yml @@ -1,6 +1,6 @@ tasks: - name: Setup - init: pnpm install && pnpm turbo build --no-daemon --go-fallback && gp sync-done setup + init: pnpm install && pnpm turbo build gp sync-done setup env: NETLIFY_URL: http://localhost:8000 DRUPAL_EXTERNAL_URL: http://localhost:8888 From d4132a4380af16393a87c7c18b052e812ead9fb9 Mon Sep 17 00:00:00 2001 From: Luqmaan Essop Date: Fri, 3 May 2024 07:24:31 +0200 Subject: [PATCH 059/134] style: typography refactoring --- .../Organisms/PageContent/BlockMarkup.tsx | 8 +-- packages/ui/src/tailwind.config.cjs | 64 ++++++++++++++++++- 2 files changed, 64 insertions(+), 8 deletions(-) diff --git a/packages/ui/src/components/Organisms/PageContent/BlockMarkup.tsx b/packages/ui/src/components/Organisms/PageContent/BlockMarkup.tsx index a83f81b79..f0f3c82a5 100644 --- a/packages/ui/src/components/Organisms/PageContent/BlockMarkup.tsx +++ b/packages/ui/src/components/Organisms/PageContent/BlockMarkup.tsx @@ -17,11 +17,7 @@ export function BlockMarkup(props: BlockMarkupFragment) {
    {unordered ? ( - + ) : null} {children} diff --git a/packages/ui/src/tailwind.config.cjs b/packages/ui/src/tailwind.config.cjs index bbdaea5bf..c2bf31165 100644 --- a/packages/ui/src/tailwind.config.cjs +++ b/packages/ui/src/tailwind.config.cjs @@ -1,9 +1,69 @@ /** @type {import('tailwindcss').Config} */ -const theme = require('./stylingAssets.json'); +const stylingAssets = require('./stylingAssets.json'); module.exports = { content: ['./src/**/*.{tsx, mdx}'], - ...theme, + theme: { + "extend": { + "typography": ({theme}) => ({ + "DEFAULT": { + css: [ + { + 'a, p a': { + }, + 'ul, ol': { + fontSize: '1.125rem', + lineHeight: '1.688rem' + }, + ol: { + }, + 'ul>li::marker, ol>li::marker': { + }, + strong: { + color: theme('colors.gray.900'), + fontWeight: '700', + }, + '.prose p': { + color: theme('colors.gray.500'), + fontSize: '1.125rem', + lineHeight: '1.688rem' + }, + '.prose a, .prose p a': { + color: theme('colors.blue.600'), + }, + '.prose em': { + color: theme('colors.gray.900'), + }, + 'prose marker': { + fontWeight: '700', + }, + 'blockquote': { + }, + '.prose blockquote p': { + fontWeight: '700', + color: '#111928' + }, + cite: { + }, + 'h1, h2, h3, h4, h5, h6': { + }, + '.prose h1': { + }, + '.prose h2': { + fontWeight: '700', + color: theme('colors.gray.900'), + }, + '.prose h3': { + }, + '.prose h4': { + } + }, + ] + } + }), + }, + ...stylingAssets.theme, + }, plugins: [ require('@tailwindcss/forms'), require('@tailwindcss/aspect-ratio'), From de0a9bd876bdbaf2fd6bc8d83b20b49b71ec47ee Mon Sep 17 00:00:00 2001 From: Luqmaan Essop Date: Fri, 3 May 2024 07:33:54 +0200 Subject: [PATCH 060/134] chore: prettier --- packages/ui/src/tailwind.config.cjs | 43 ++++++++++++----------------- 1 file changed, 17 insertions(+), 26 deletions(-) diff --git a/packages/ui/src/tailwind.config.cjs b/packages/ui/src/tailwind.config.cjs index c2bf31165..031674664 100644 --- a/packages/ui/src/tailwind.config.cjs +++ b/packages/ui/src/tailwind.config.cjs @@ -4,21 +4,18 @@ const stylingAssets = require('./stylingAssets.json'); module.exports = { content: ['./src/**/*.{tsx, mdx}'], theme: { - "extend": { - "typography": ({theme}) => ({ - "DEFAULT": { + extend: { + typography: ({ theme }) => ({ + DEFAULT: { css: [ { - 'a, p a': { - }, + 'a, p a': {}, 'ul, ol': { fontSize: '1.125rem', - lineHeight: '1.688rem' - }, - ol: { - }, - 'ul>li::marker, ol>li::marker': { + lineHeight: '1.688rem', }, + ol: {}, + 'ul>li::marker, ol>li::marker': {}, strong: { color: theme('colors.gray.900'), fontWeight: '700', @@ -26,7 +23,7 @@ module.exports = { '.prose p': { color: theme('colors.gray.500'), fontSize: '1.125rem', - lineHeight: '1.688rem' + lineHeight: '1.688rem', }, '.prose a, .prose p a': { color: theme('colors.blue.600'), @@ -37,29 +34,23 @@ module.exports = { 'prose marker': { fontWeight: '700', }, - 'blockquote': { - }, + blockquote: {}, '.prose blockquote p': { fontWeight: '700', - color: '#111928' - }, - cite: { - }, - 'h1, h2, h3, h4, h5, h6': { - }, - '.prose h1': { + color: '#111928', }, + cite: {}, + 'h1, h2, h3, h4, h5, h6': {}, + '.prose h1': {}, '.prose h2': { fontWeight: '700', color: theme('colors.gray.900'), }, - '.prose h3': { - }, - '.prose h4': { - } + '.prose h3': {}, + '.prose h4': {}, }, - ] - } + ], + }, }), }, ...stylingAssets.theme, From bc16754dc379d355cff4ef43cd232e46613bb2f1 Mon Sep 17 00:00:00 2001 From: Luqmaan Essop Date: Fri, 3 May 2024 07:49:07 +0200 Subject: [PATCH 061/134] style: typography weight on p a --- packages/ui/src/tailwind.config.cjs | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/ui/src/tailwind.config.cjs b/packages/ui/src/tailwind.config.cjs index 031674664..c6f9477ec 100644 --- a/packages/ui/src/tailwind.config.cjs +++ b/packages/ui/src/tailwind.config.cjs @@ -27,6 +27,7 @@ module.exports = { }, '.prose a, .prose p a': { color: theme('colors.blue.600'), + fontWeight: '400', }, '.prose em': { color: theme('colors.gray.900'), From 970d0558611b19c3f5b86e1f23d48b7e594ca91b Mon Sep 17 00:00:00 2001 From: Luqmaan Essop Date: Fri, 3 May 2024 20:25:08 +0200 Subject: [PATCH 062/134] style: adjustments --- .../ui/src/components/Organisms/PageContent/BlockMarkup.tsx | 3 ++- packages/ui/src/tailwind.config.cjs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/components/Organisms/PageContent/BlockMarkup.tsx b/packages/ui/src/components/Organisms/PageContent/BlockMarkup.tsx index f0f3c82a5..1b4f20d0c 100644 --- a/packages/ui/src/components/Organisms/PageContent/BlockMarkup.tsx +++ b/packages/ui/src/components/Organisms/PageContent/BlockMarkup.tsx @@ -17,7 +17,8 @@ export function BlockMarkup(props: BlockMarkupFragment) {
    li::marker, ol>li::marker': {}, strong: { color: theme('colors.gray.900'), From 66c89f6ca2c9196594404ed45df2b746c9cac8b7 Mon Sep 17 00:00:00 2001 From: Luqmaan Essop Date: Fri, 3 May 2024 20:54:07 +0200 Subject: [PATCH 063/134] chore: prettier --- packages/ui/src/tailwind.config.cjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/src/tailwind.config.cjs b/packages/ui/src/tailwind.config.cjs index 2bdeee963..e66fff3a7 100644 --- a/packages/ui/src/tailwind.config.cjs +++ b/packages/ui/src/tailwind.config.cjs @@ -13,7 +13,7 @@ module.exports = { 'ul, ol': { fontSize: '1.125rem', lineHeight: '1.688rem', - paddingLeft: '2.5rem' + paddingLeft: '2.5rem', }, 'ul>li::marker, ol>li::marker': {}, strong: { From 101febc2d7b2561c58d2673db69414fdc5817522 Mon Sep 17 00:00:00 2001 From: Luqmaan Essop Date: Thu, 2 May 2024 16:20:00 +0200 Subject: [PATCH 064/134] refactor: refactor parent menu item to be clickable --- packages/ui/src/components/Organisms/Header.tsx | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/packages/ui/src/components/Organisms/Header.tsx b/packages/ui/src/components/Organisms/Header.tsx index dacd4f058..d8d05907b 100644 --- a/packages/ui/src/components/Organisms/Header.tsx +++ b/packages/ui/src/components/Organisms/Header.tsx @@ -76,6 +76,13 @@ export function Header() { ) : ( + + {item.title} + {item.children.map((child) => child.children.length === 0 ? ( + + {item.title} + {item.children.map((child) => child.children.length === 0 ? ( Date: Tue, 7 May 2024 16:54:05 +0200 Subject: [PATCH 065/134] feat: breadcrumbs implementation --- .../Molecules/Breadcrumbs.stories.tsx | 24 +++++ .../src/components/Molecules/Breadcrumbs.tsx | 66 ++++++++++++++ packages/ui/src/components/Routes/Menu.tsx | 90 +++++++++++++++++++ 3 files changed, 180 insertions(+) create mode 100644 packages/ui/src/components/Molecules/Breadcrumbs.stories.tsx create mode 100644 packages/ui/src/components/Molecules/Breadcrumbs.tsx create mode 100644 packages/ui/src/components/Routes/Menu.tsx diff --git a/packages/ui/src/components/Molecules/Breadcrumbs.stories.tsx b/packages/ui/src/components/Molecules/Breadcrumbs.stories.tsx new file mode 100644 index 000000000..a0ca464d7 --- /dev/null +++ b/packages/ui/src/components/Molecules/Breadcrumbs.stories.tsx @@ -0,0 +1,24 @@ +import { FrameQuery, OperationExecutor } from '@custom/schema'; +import { Meta, StoryObj } from '@storybook/react'; +import React from 'react'; + +import { Default as FrameStory } from '../Routes/Frame.stories'; +import BreadCrumbs from './Breadcrumbs'; + +export default { + component: BreadCrumbs, + parameters: { + layout: 'fullscreen', + location: { pathname: '/gatsby-turbo' }, + }, +} satisfies StoryObj; + +export const Default = { + render: () => { + return ( + + + + ); + }, +}; diff --git a/packages/ui/src/components/Molecules/Breadcrumbs.tsx b/packages/ui/src/components/Molecules/Breadcrumbs.tsx new file mode 100644 index 000000000..7f0811eb4 --- /dev/null +++ b/packages/ui/src/components/Molecules/Breadcrumbs.tsx @@ -0,0 +1,66 @@ +import { Link } from '@custom/schema'; +import { ChevronRightIcon } from '@heroicons/react/24/outline'; +import clsx from 'clsx'; +import React from 'react'; + +import { isTruthy } from '../../utils/isTruthy'; +import { useBreadcrumbs } from '../Routes/Menu'; + +export default function BreadCrumbs({ + bgCol = 'gray-50', + className, +}: { + bgCol?: string; + className?: string; +}) { + const breadcrumbs = useBreadcrumbs(); + + console.log('breadcrumbs:', breadcrumbs); + + if (!breadcrumbs.length) { + console.log('breadcrumbs null:'); + return null; + } + + return ( + + ); +} diff --git a/packages/ui/src/components/Routes/Menu.tsx b/packages/ui/src/components/Routes/Menu.tsx new file mode 100644 index 000000000..2721f1a90 --- /dev/null +++ b/packages/ui/src/components/Routes/Menu.tsx @@ -0,0 +1,90 @@ +import { FrameQuery, NavigationItem, Url, useLocation } from '@custom/schema'; +import { useIntl } from 'react-intl'; + +import { useOperation } from '../../utils/operation'; + +export type MenuNameType = 'main' | 'footer'; + +export function useMenus() { + const intl = useIntl(); + const locale = intl.locale; + const settings = useOperation(FrameQuery).data; + return { + main: settings?.mainNavigation + ?.filter((nav) => nav?.locale === locale) + .pop(), + footer: settings?.footerNavigation + ?.filter((nav) => nav?.locale === locale) + .pop(), + }; +} + +export function useCurrentPath() { + const [loc] = useLocation(); + return loc.pathname; +} + +export function useMenuItem(path: string, menuName: MenuNameType) { + const menus = useMenus(); + return ( + menus && + menus[menuName]?.items.find((menuItem) => menuItem?.target === path) + ); +} + +export function useMenuChildren(path: string, menuName: MenuNameType) { + const menus = useMenus(); + const menuItemFromPath = useMenuItem(path, menuName); + return ( + menus && + menus[menuName]?.items.filter( + (menuItem) => menuItem?.parent === menuItemFromPath?.id, + ) + ); +} + +export function useCurrentMenuItem(menuName: MenuNameType) { + const currentPath = useCurrentPath(); + return useMenuItem(currentPath || '', menuName); +} + +export function useCurrentMenuChildren(menuName: MenuNameType) { + const currentPath = useCurrentPath(); + return useMenuChildren(currentPath || '', menuName); +} + +export function useMenuAncestors(path: string, menuName: MenuNameType) { + const menus = useMenus(); + const menuItemFromPath = useMenuItem(path, menuName); + let processingMenuItem = menuItemFromPath; + const ancestors: Array = []; + + // Set current page breadcrumb + if (typeof processingMenuItem !== 'undefined') { + ancestors.push(processingMenuItem); + } + + while ( + typeof processingMenuItem !== 'undefined' && + processingMenuItem?.parent + ) { + processingMenuItem = + menus && + menus[menuName]?.items.find( + (menuItem) => menuItem?.id === processingMenuItem?.parent, + ); + if (typeof processingMenuItem !== 'undefined') { + ancestors.push(processingMenuItem); + } + } + if (ancestors.length > 0) { + ancestors.push({ id: '_', target: '/' as Url, title: 'Home' }); + } + + return ancestors.reverse(); +} + +export const useBreadcrumbs = (menuName?: MenuNameType, path?: string) => { + const currentPath = useCurrentPath(); + return useMenuAncestors(path || currentPath || '', menuName || 'main'); +}; From bee3b2c5938a3bd5af4f6c6656e050c516ba3ebd Mon Sep 17 00:00:00 2001 From: Luqmaan Essop Date: Tue, 7 May 2024 20:20:23 +0200 Subject: [PATCH 066/134] feat: wip --- .../components/Molecules/Breadcrumbs.stories.tsx | 6 +++--- .../ui/src/components/Molecules/Breadcrumbs.tsx | 13 ++----------- 2 files changed, 5 insertions(+), 14 deletions(-) diff --git a/packages/ui/src/components/Molecules/Breadcrumbs.stories.tsx b/packages/ui/src/components/Molecules/Breadcrumbs.stories.tsx index a0ca464d7..81dd002ba 100644 --- a/packages/ui/src/components/Molecules/Breadcrumbs.stories.tsx +++ b/packages/ui/src/components/Molecules/Breadcrumbs.stories.tsx @@ -1,5 +1,5 @@ import { FrameQuery, OperationExecutor } from '@custom/schema'; -import { Meta, StoryObj } from '@storybook/react'; +import { Meta } from '@storybook/react'; import React from 'react'; import { Default as FrameStory } from '../Routes/Frame.stories'; @@ -9,9 +9,9 @@ export default { component: BreadCrumbs, parameters: { layout: 'fullscreen', - location: { pathname: '/gatsby-turbo' }, + location: new URL('local:/gatsby-turbo'), }, -} satisfies StoryObj; +} satisfies Meta; export const Default = { render: () => { diff --git a/packages/ui/src/components/Molecules/Breadcrumbs.tsx b/packages/ui/src/components/Molecules/Breadcrumbs.tsx index 7f0811eb4..f057661ce 100644 --- a/packages/ui/src/components/Molecules/Breadcrumbs.tsx +++ b/packages/ui/src/components/Molecules/Breadcrumbs.tsx @@ -6,19 +6,10 @@ import React from 'react'; import { isTruthy } from '../../utils/isTruthy'; import { useBreadcrumbs } from '../Routes/Menu'; -export default function BreadCrumbs({ - bgCol = 'gray-50', - className, -}: { - bgCol?: string; - className?: string; -}) { +export default function BreadCrumbs({ className }: { className?: string }) { const breadcrumbs = useBreadcrumbs(); - console.log('breadcrumbs:', breadcrumbs); - if (!breadcrumbs.length) { - console.log('breadcrumbs null:'); return null; } @@ -27,7 +18,7 @@ export default function BreadCrumbs({ className={clsx('pt-5 max-w-screen-xl mx-auto', className)} aria-label="Breadcrumb" > -
      +
        {breadcrumbs?.filter(isTruthy).map(({ title, target, id }, index) => (
      1. {index > 0 ? ( From 42f3036294ddd83799d422039b937289330b8936 Mon Sep 17 00:00:00 2001 From: Christophe Jossart Date: Tue, 7 May 2024 21:45:22 +0200 Subject: [PATCH 067/134] fix(SLB-299): last border --- .../src/components/Organisms/PageContent/BlockAccordion.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/components/Organisms/PageContent/BlockAccordion.tsx b/packages/ui/src/components/Organisms/PageContent/BlockAccordion.tsx index 5f4e19453..9838e91a3 100644 --- a/packages/ui/src/components/Organisms/PageContent/BlockAccordion.tsx +++ b/packages/ui/src/components/Organisms/PageContent/BlockAccordion.tsx @@ -21,8 +21,8 @@ const accordionTheme: CustomFlowbiteTheme['accordion'] = { root: { base: 'mt-10 divide-y divide-gray-200 border-gray-200 dark:divide-gray-700 dark:border-gray-700', flush: { - off: 'border-b last:border-0', - on: 'border-b last:border-0', + off: 'last:border-0', + on: 'last:border-0', }, }, content: { From 7b58f2ea3241e41cd3580e75066c5ec5d13a0caa Mon Sep 17 00:00:00 2001 From: Luqmaan Essop Date: Wed, 8 May 2024 09:39:42 +0200 Subject: [PATCH 068/134] feat: wip --- .../Molecules/Breadcrumbs.stories.tsx | 2 +- .../src/components/Molecules/Breadcrumbs.tsx | 9 +++---- .../src/components/Organisms/PageDisplay.tsx | 10 +++++++- packages/ui/src/components/Routes/Menu.tsx | 4 ++- .../ui/src/components/Routes/Page.stories.tsx | 25 ++++++++++++++----- 5 files changed, 35 insertions(+), 15 deletions(-) diff --git a/packages/ui/src/components/Molecules/Breadcrumbs.stories.tsx b/packages/ui/src/components/Molecules/Breadcrumbs.stories.tsx index 81dd002ba..9a7c6d0e5 100644 --- a/packages/ui/src/components/Molecules/Breadcrumbs.stories.tsx +++ b/packages/ui/src/components/Molecules/Breadcrumbs.stories.tsx @@ -3,7 +3,7 @@ import { Meta } from '@storybook/react'; import React from 'react'; import { Default as FrameStory } from '../Routes/Frame.stories'; -import BreadCrumbs from './Breadcrumbs'; +import { BreadCrumbs } from './Breadcrumbs'; export default { component: BreadCrumbs, diff --git a/packages/ui/src/components/Molecules/Breadcrumbs.tsx b/packages/ui/src/components/Molecules/Breadcrumbs.tsx index f057661ce..0efa3eb61 100644 --- a/packages/ui/src/components/Molecules/Breadcrumbs.tsx +++ b/packages/ui/src/components/Molecules/Breadcrumbs.tsx @@ -6,7 +6,7 @@ import React from 'react'; import { isTruthy } from '../../utils/isTruthy'; import { useBreadcrumbs } from '../Routes/Menu'; -export default function BreadCrumbs({ className }: { className?: string }) { +export function BreadCrumbs({ className }: { className?: string }) { const breadcrumbs = useBreadcrumbs(); if (!breadcrumbs.length) { @@ -14,11 +14,8 @@ export default function BreadCrumbs({ className }: { className?: string }) { } return ( -