@amazeelabs/silverback-gutenberg 2.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +983 -0
- package/drupal/silverback_gutenberg/README.md +439 -0
- package/drupal/silverback_gutenberg/composer.json +20 -0
- package/drupal/silverback_gutenberg/config/install/silverback_gutenberg.settings.yml +1 -0
- package/drupal/silverback_gutenberg/config/schema/silverback_gutenberg.schema.yml +13 -0
- package/drupal/silverback_gutenberg/css/gutenberg-tweaks.css +46 -0
- package/drupal/silverback_gutenberg/directives.gql +40 -0
- package/drupal/silverback_gutenberg/directives.graphql +46 -0
- package/drupal/silverback_gutenberg/js/base.js +24 -0
- package/drupal/silverback_gutenberg/js/gutenberg-tweaks.js +154 -0
- package/drupal/silverback_gutenberg/silverback_gutenberg.api.php +76 -0
- package/drupal/silverback_gutenberg/silverback_gutenberg.info.yml +8 -0
- package/drupal/silverback_gutenberg/silverback_gutenberg.libraries.yml +14 -0
- package/drupal/silverback_gutenberg/silverback_gutenberg.module +97 -0
- package/drupal/silverback_gutenberg/silverback_gutenberg.services.yml +29 -0
- package/drupal/silverback_gutenberg/src/Annotation/GutenbergBlockMutator.php +39 -0
- package/drupal/silverback_gutenberg/src/Annotation/GutenbergValidator.php +37 -0
- package/drupal/silverback_gutenberg/src/Annotation/GutenbergValidatorRule.php +37 -0
- package/drupal/silverback_gutenberg/src/Attribute/GutenbergBlockMutator.php +29 -0
- package/drupal/silverback_gutenberg/src/BlockMutator/BlockMutatorBase.php +24 -0
- package/drupal/silverback_gutenberg/src/BlockMutator/BlockMutatorInterface.php +41 -0
- package/drupal/silverback_gutenberg/src/BlockMutator/BlockMutatorManager.php +114 -0
- package/drupal/silverback_gutenberg/src/BlockMutator/BlockMutatorManagerInterface.php +30 -0
- package/drupal/silverback_gutenberg/src/BlockMutator/EntityBlockMutatorBase.php +189 -0
- package/drupal/silverback_gutenberg/src/BlockSerializer.php +84 -0
- package/drupal/silverback_gutenberg/src/Controller/LinkitAutocomplete.php +84 -0
- package/drupal/silverback_gutenberg/src/Directives.php +74 -0
- package/drupal/silverback_gutenberg/src/EditorBlocksProcessor.php +53 -0
- package/drupal/silverback_gutenberg/src/GutenbergValidation/GutenbergCardinalityValidatorInterface.php +19 -0
- package/drupal/silverback_gutenberg/src/GutenbergValidation/GutenbergCardinalityValidatorTrait.php +221 -0
- package/drupal/silverback_gutenberg/src/GutenbergValidation/GutenbergValidatorBase.php +24 -0
- package/drupal/silverback_gutenberg/src/GutenbergValidation/GutenbergValidatorInterface.php +65 -0
- package/drupal/silverback_gutenberg/src/GutenbergValidation/GutenbergValidatorManager.php +37 -0
- package/drupal/silverback_gutenberg/src/GutenbergValidation/GutenbergValidatorRuleInterface.php +20 -0
- package/drupal/silverback_gutenberg/src/GutenbergValidation/GutenbergValidatorRuleManager.php +37 -0
- package/drupal/silverback_gutenberg/src/LinkProcessor.php +405 -0
- package/drupal/silverback_gutenberg/src/LinkedContentExtractor.php +35 -0
- package/drupal/silverback_gutenberg/src/Normalizer/GutenbergContentEntityNormalizer.php +123 -0
- package/drupal/silverback_gutenberg/src/Plugin/EntityUsage/Track/GutenbergContentTrackTrait.php +51 -0
- package/drupal/silverback_gutenberg/src/Plugin/EntityUsage/Track/GutenbergLinkedContent.php +96 -0
- package/drupal/silverback_gutenberg/src/Plugin/EntityUsage/Track/GutenbergMediaEmbed.php +63 -0
- package/drupal/silverback_gutenberg/src/Plugin/EntityUsage/Track/GutenbergReferencedContent.php +101 -0
- package/drupal/silverback_gutenberg/src/Plugin/GraphQL/DataProducer/EditorBlockAttribute.php +42 -0
- package/drupal/silverback_gutenberg/src/Plugin/GraphQL/DataProducer/EditorBlockChildren.php +32 -0
- package/drupal/silverback_gutenberg/src/Plugin/GraphQL/DataProducer/EditorBlockHtml.php +30 -0
- package/drupal/silverback_gutenberg/src/Plugin/GraphQL/DataProducer/EditorBlockMedia.php +159 -0
- package/drupal/silverback_gutenberg/src/Plugin/GraphQL/DataProducer/EditorBlockType.php +29 -0
- package/drupal/silverback_gutenberg/src/Plugin/GraphQL/DataProducer/EditorBlocks.php +127 -0
- package/drupal/silverback_gutenberg/src/Plugin/GraphQL/Directive/EditorBlockAttribute.php +29 -0
- package/drupal/silverback_gutenberg/src/Plugin/GraphQL/Directive/EditorBlockChildren.php +21 -0
- package/drupal/silverback_gutenberg/src/Plugin/GraphQL/Directive/EditorBlockMarkup.php +21 -0
- package/drupal/silverback_gutenberg/src/Plugin/GraphQL/Directive/EditorBlockMedia.php +21 -0
- package/drupal/silverback_gutenberg/src/Plugin/GraphQL/Directive/EditorBlockType.php +21 -0
- package/drupal/silverback_gutenberg/src/Plugin/GraphQL/Directive/EditorBlocks.php +36 -0
- package/drupal/silverback_gutenberg/src/Plugin/GutenbergBlockMutator/MediaBlockMutator.php +30 -0
- package/drupal/silverback_gutenberg/src/Plugin/GutenbergBlockMutator/NodeBlockMutator.php +25 -0
- package/drupal/silverback_gutenberg/src/Plugin/GutenbergBlockMutator/TermReferenceBlockMutator.php +104 -0
- package/drupal/silverback_gutenberg/src/Plugin/Linkit/Matcher/SilverbackMatcherTrait.php +69 -0
- package/drupal/silverback_gutenberg/src/Plugin/Linkit/Matcher/SilverbackMediaMatcher.php +53 -0
- package/drupal/silverback_gutenberg/src/Plugin/Linkit/Matcher/SilverbackNodeMatcher.php +19 -0
- package/drupal/silverback_gutenberg/src/Plugin/Validation/Constraint/Gutenberg.php +15 -0
- package/drupal/silverback_gutenberg/src/Plugin/Validation/Constraint/GutenbergValidator.php +210 -0
- package/drupal/silverback_gutenberg/src/Plugin/Validation/GutenbergValidatorRule/Email.php +28 -0
- package/drupal/silverback_gutenberg/src/Plugin/Validation/GutenbergValidatorRule/Required.php +29 -0
- package/drupal/silverback_gutenberg/src/ReferencedContentExtractor.php +67 -0
- package/drupal/silverback_gutenberg/src/Routing/RouteSubscriber.php +17 -0
- package/drupal/silverback_gutenberg/src/Service/MediaService.php +27 -0
- package/drupal/silverback_gutenberg/src/SilverbackGutenbergServiceProvider.php +28 -0
- package/drupal/silverback_gutenberg/src/Utils.php +30 -0
- package/drupal/silverback_gutenberg/src/WebformMessageManager.php +29 -0
- package/drupal/silverback_gutenberg/tests/graphql/.graphqlrc.json +5 -0
- package/drupal/silverback_gutenberg/tests/graphql/queries/editor.gql +30 -0
- package/drupal/silverback_gutenberg/tests/graphql/schema.graphqls +37 -0
- package/drupal/silverback_gutenberg/tests/modules/silverback_gutenberg_test_validator/silverback_gutenberg_test_validator.info.yml +9 -0
- package/drupal/silverback_gutenberg/tests/modules/silverback_gutenberg_test_validator/src/Plugin/Validation/GutenbergValidator/ColumnValidator.php +42 -0
- package/drupal/silverback_gutenberg/tests/modules/silverback_gutenberg_test_validator/src/Plugin/Validation/GutenbergValidator/GroupValidator.php +50 -0
- package/drupal/silverback_gutenberg/tests/modules/silverback_gutenberg_test_validator/src/Plugin/Validation/GutenbergValidator/LinkValidator.php +43 -0
- package/drupal/silverback_gutenberg/tests/src/Kernel/BlockValidationRuleTest.php +194 -0
- package/drupal/silverback_gutenberg/tests/src/Kernel/EditorDirectivesTest.php +255 -0
- package/drupal/silverback_gutenberg/tests/src/Kernel/GutenbergLinkedContentEUTrackTest.php +133 -0
- package/drupal/silverback_gutenberg/tests/src/Kernel/GutenbergReferencedContentEUTrackTest.php +225 -0
- package/drupal/silverback_gutenberg/tests/src/Kernel/LinkProcessorTest.php +284 -0
- package/drupal/silverback_gutenberg/tests/src/Kernel/MediaNormalizerTest.php +174 -0
- package/drupal/silverback_gutenberg/tests/src/Traits/SampleAssetTrait.php +15 -0
- package/drupal/silverback_gutenberg/tests/src/Unit/BlockSerializerTest.php +27 -0
- package/drupal/silverback_gutenberg/tests/src/Unit/BlockValidatorCardinalityTest.php +1537 -0
- package/drupal/silverback_gutenberg/tests/src/Unit/EditorBlocksProcessorTest.php +159 -0
- package/drupal/silverback_gutenberg/tests/src/Unit/LinkedContentExtractorTest.php +65 -0
- package/drupal/silverback_gutenberg/tests/src/Unit/ReferencedContentExtractorTest.php +248 -0
- package/drupal/silverback_gutenberg/tests/src/assets/media/data.json +4 -0
- package/drupal/silverback_gutenberg/tests/src/assets/media/source.html +71 -0
- package/drupal/silverback_gutenberg/tests/src/assets/media/target.html +71 -0
- package/package.json +16 -0
- package/turbo.json +15 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Drupal\silverback_gutenberg\Plugin\Validation\GutenbergValidatorRule;
|
|
4
|
+
|
|
5
|
+
use Drupal\silverback_gutenberg\GutenbergValidation\GutenbergValidatorRuleInterface;
|
|
6
|
+
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @GutenbergValidatorRule(
|
|
10
|
+
* id="email",
|
|
11
|
+
* label = @Translation("Email")
|
|
12
|
+
* )
|
|
13
|
+
*/
|
|
14
|
+
class Email implements GutenbergValidatorRuleInterface {
|
|
15
|
+
|
|
16
|
+
use StringTranslationTrait;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* {@inheritDoc}
|
|
20
|
+
*/
|
|
21
|
+
public function validate($value, $fieldLabel): bool|string {
|
|
22
|
+
if (!empty($value) && !\Drupal::service('email.validator')->isValid($value)) {
|
|
23
|
+
return $this->t('%field is not valid.', ['%field' => $fieldLabel]);
|
|
24
|
+
}
|
|
25
|
+
return TRUE;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Drupal\silverback_gutenberg\Plugin\Validation\GutenbergValidatorRule;
|
|
4
|
+
|
|
5
|
+
use Drupal\silverback_gutenberg\GutenbergValidation\GutenbergValidatorRuleInterface;
|
|
6
|
+
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @GutenbergValidatorRule(
|
|
10
|
+
* id="required",
|
|
11
|
+
* label = @Translation("Required")
|
|
12
|
+
* )
|
|
13
|
+
*/
|
|
14
|
+
class Required implements GutenbergValidatorRuleInterface {
|
|
15
|
+
|
|
16
|
+
use StringTranslationTrait;
|
|
17
|
+
public $requiredMessage = '%field field is required.';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* {@inheritDoc}
|
|
21
|
+
*/
|
|
22
|
+
public function validate($value, $fieldLabel): bool|string {
|
|
23
|
+
if (empty($value) || $value === '_none') {
|
|
24
|
+
return $this->t($this->requiredMessage, ['%field' => $fieldLabel]);
|
|
25
|
+
}
|
|
26
|
+
return TRUE;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Drupal\silverback_gutenberg;
|
|
4
|
+
|
|
5
|
+
class ReferencedContentExtractor {
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Given an array of Gutenberg blocks, it returns all the referenced content
|
|
9
|
+
* based on specific entity type and uuid attributes.
|
|
10
|
+
*
|
|
11
|
+
* @param array $gutenbergBlocks
|
|
12
|
+
* An array of parsed gutenberg blocks.
|
|
13
|
+
* @param string $entityTypeAttribute
|
|
14
|
+
* The attribute name which identifies the entity type.
|
|
15
|
+
* @param string $uuidAttribute
|
|
16
|
+
* The attribute name which identifies the uuid.
|
|
17
|
+
* @return array
|
|
18
|
+
*/
|
|
19
|
+
public function getTargetEntities(
|
|
20
|
+
array $gutenbergBlocks,
|
|
21
|
+
string $entityTypeAttribute = 'entityType',
|
|
22
|
+
string $uuidAttribute = 'uuid'
|
|
23
|
+
) {
|
|
24
|
+
$targetEntities = [];
|
|
25
|
+
$this->extractReferencesFromGutenbergBlocks($gutenbergBlocks, $entityTypeAttribute, $uuidAttribute, $targetEntities);
|
|
26
|
+
return $targetEntities;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Recursively extracts the content that is referenced inside an array of
|
|
31
|
+
* Gutenberg blocks. This is basically a helper for the public
|
|
32
|
+
* getTargetEntities() method.
|
|
33
|
+
*
|
|
34
|
+
* @param array $blocks
|
|
35
|
+
* An array of parsed gutenberg blocks.
|
|
36
|
+
* @param string $entityTypeAttribute
|
|
37
|
+
* The attribute name which identifies the entity type.
|
|
38
|
+
* @param string $uuidAttribute
|
|
39
|
+
* The attribute name which identifies the uuid.
|
|
40
|
+
* @param array $references
|
|
41
|
+
* An array with all the extracted references until the current call.
|
|
42
|
+
* @return void
|
|
43
|
+
*/
|
|
44
|
+
protected function extractReferencesFromGutenbergBlocks(
|
|
45
|
+
array $blocks,
|
|
46
|
+
string $entityTypeAttribute,
|
|
47
|
+
string $uuidAttribute,
|
|
48
|
+
array &$references
|
|
49
|
+
) {
|
|
50
|
+
foreach ($blocks as $block) {
|
|
51
|
+
if (!empty($block['attrs'][$uuidAttribute])) {
|
|
52
|
+
$uuid = $block['attrs'][$uuidAttribute];
|
|
53
|
+
// The default entity type should be, for convenience, node.
|
|
54
|
+
$entityType = $block['attrs'][$entityTypeAttribute] ?? 'node';
|
|
55
|
+
// Usually, the uuid would be one single value, but we also support an
|
|
56
|
+
// array of values.
|
|
57
|
+
$uuidList = is_array($uuid) ? $uuid : [$uuid];
|
|
58
|
+
foreach ($uuidList as $uuidItem) {
|
|
59
|
+
$references[$entityType][$uuidItem] = $uuidItem;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (!empty($block['innerBlocks'])) {
|
|
63
|
+
$this->extractReferencesFromGutenbergBlocks($block['innerBlocks'], $entityTypeAttribute, $uuidAttribute, $references);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Drupal\silverback_gutenberg\Routing;
|
|
4
|
+
|
|
5
|
+
use Drupal\Core\Routing\RouteSubscriberBase;
|
|
6
|
+
use Drupal\silverback_gutenberg\Controller\LinkitAutocomplete;
|
|
7
|
+
use Symfony\Component\Routing\RouteCollection;
|
|
8
|
+
|
|
9
|
+
class RouteSubscriber extends RouteSubscriberBase {
|
|
10
|
+
|
|
11
|
+
protected function alterRoutes(RouteCollection $collection) {
|
|
12
|
+
if ($route = $collection->get('gutenberg.content.search')) {
|
|
13
|
+
$route->setDefault('_controller', LinkitAutocomplete::class . '::search');
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Drupal\silverback_gutenberg\Service;
|
|
4
|
+
|
|
5
|
+
use Drupal\gutenberg\Service\MediaService as Original;
|
|
6
|
+
use Drupal\media_library\MediaLibraryState;
|
|
7
|
+
|
|
8
|
+
class MediaService extends Original {
|
|
9
|
+
|
|
10
|
+
public function renderDialog(array $media_types, array $media_bundles = NULL) {
|
|
11
|
+
// Instead of guessing media types as the parent method does, use given
|
|
12
|
+
// media types as media type IDs. So the blocks have full control over
|
|
13
|
+
// allowed media types.
|
|
14
|
+
// The default behavior is: get bundles by $media_types, then intersect them
|
|
15
|
+
// with $media_bundles.
|
|
16
|
+
// New behavior is: get bundles from $media_types, so we don't break
|
|
17
|
+
// projects using this module.
|
|
18
|
+
$media_bundles = $media_types;
|
|
19
|
+
|
|
20
|
+
$buildUi = $this->builder->buildUi(
|
|
21
|
+
MediaLibraryState::create('gutenberg.media_library.opener', array_unique($media_bundles), reset($media_bundles), 1)
|
|
22
|
+
);
|
|
23
|
+
$this->moduleHandler->alter('gutenberg_media_library_view', $buildUi);
|
|
24
|
+
return $this->renderer->render($buildUi);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Drupal\silverback_gutenberg;
|
|
4
|
+
|
|
5
|
+
use Drupal\Core\DependencyInjection\ContainerBuilder;
|
|
6
|
+
use Drupal\Core\DependencyInjection\ServiceProviderBase;
|
|
7
|
+
use Drupal\silverback_gutenberg\Normalizer\GutenbergContentEntityNormalizer;
|
|
8
|
+
use Drupal\silverback_gutenberg\Service\MediaService;
|
|
9
|
+
use Symfony\Component\DependencyInjection\Reference;
|
|
10
|
+
|
|
11
|
+
class SilverbackGutenbergServiceProvider extends ServiceProviderBase {
|
|
12
|
+
public function alter(ContainerBuilder $container) {
|
|
13
|
+
if ($container->has('gutenberg.media_service')) {
|
|
14
|
+
$definition = $container->getDefinition('gutenberg.media_service');
|
|
15
|
+
$definition->setClass(MediaService::class);
|
|
16
|
+
}
|
|
17
|
+
if ($container->has('webform.message_manager')) {
|
|
18
|
+
$definition = $container->getDefinition('webform.message_manager');
|
|
19
|
+
$definition->setClass(WebformMessageManager::class);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if ($container->has('default_content.content_entity_normalizer')) {
|
|
23
|
+
$definition = $container->getDefinition('default_content.content_entity_normalizer');
|
|
24
|
+
$definition->setClass(GutenbergContentEntityNormalizer::class);
|
|
25
|
+
$definition->addArgument(new Reference('plugin.manager.block_mutator'));
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Drupal\silverback_gutenberg;
|
|
4
|
+
|
|
5
|
+
use Drupal\Core\Entity\EntityInterface;
|
|
6
|
+
use Drupal\Core\Entity\FieldableEntityInterface;
|
|
7
|
+
use Drupal\gutenberg\Controller\UtilsController;
|
|
8
|
+
|
|
9
|
+
class Utils {
|
|
10
|
+
|
|
11
|
+
public static function getGutenbergFields(EntityInterface $entity): array {
|
|
12
|
+
if (!function_exists('_gutenberg_is_gutenberg_enabled') || !_gutenberg_is_gutenberg_enabled($entity)) {
|
|
13
|
+
return [];
|
|
14
|
+
}
|
|
15
|
+
if ($entity instanceof FieldableEntityInterface) {
|
|
16
|
+
$textFields = UtilsController::getEntityTextFields($entity);
|
|
17
|
+
if (empty($textFields)) {
|
|
18
|
+
return [];
|
|
19
|
+
}
|
|
20
|
+
// At the moment Gutenberg uses the first text field.
|
|
21
|
+
return [$textFields[0]];
|
|
22
|
+
}
|
|
23
|
+
return [];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public static function linkProcessor(): LinkProcessor {
|
|
27
|
+
return \Drupal::service(LinkProcessor::class);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Drupal\silverback_gutenberg;
|
|
4
|
+
|
|
5
|
+
use Drupal\webform\WebformMessageManager as Original;
|
|
6
|
+
|
|
7
|
+
class WebformMessageManager extends Original {
|
|
8
|
+
|
|
9
|
+
public function build($key) {
|
|
10
|
+
$build = parent::build($key);
|
|
11
|
+
|
|
12
|
+
if (isset($build['#markup'])) {
|
|
13
|
+
/** @var \Drupal\silverback_gutenberg\LinkProcessor $linkProcessor */
|
|
14
|
+
$linkProcessor = \Drupal::service(LinkProcessor::class);
|
|
15
|
+
|
|
16
|
+
// Ensure that the redirect links point to correct URLs.
|
|
17
|
+
$build['#markup'] = $linkProcessor->processLinks($build['#markup'], 'inbound');
|
|
18
|
+
$build['#markup'] = $linkProcessor->processLinks(
|
|
19
|
+
$build['#markup'],
|
|
20
|
+
'outbound',
|
|
21
|
+
\Drupal::languageManager()->getCurrentLanguage()
|
|
22
|
+
);
|
|
23
|
+
$build['#cache']['max-age'] = 0;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return $build;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
fragment Page on Page {
|
|
2
|
+
title
|
|
3
|
+
content {
|
|
4
|
+
__typename
|
|
5
|
+
... on Text {
|
|
6
|
+
content
|
|
7
|
+
}
|
|
8
|
+
... on Figure {
|
|
9
|
+
caption
|
|
10
|
+
image {
|
|
11
|
+
alt
|
|
12
|
+
}
|
|
13
|
+
imageAlt
|
|
14
|
+
}
|
|
15
|
+
... on Columns {
|
|
16
|
+
columns {
|
|
17
|
+
__typename
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
query {
|
|
24
|
+
en {
|
|
25
|
+
...Page
|
|
26
|
+
}
|
|
27
|
+
de {
|
|
28
|
+
...Page
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
type Query {
|
|
2
|
+
en: Page @loadEntity(type: "node", id: "1")
|
|
3
|
+
de: Page @loadEntity(type: "node", id: "1") @entityTranslation(lang: "de")
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
type Page {
|
|
7
|
+
title: String! @property(path: "title.value")
|
|
8
|
+
content: [Blocks]!
|
|
9
|
+
@resolveEditorBlocks(
|
|
10
|
+
path: "body.value"
|
|
11
|
+
aggregated: ["core/paragraph", "core/list"]
|
|
12
|
+
)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
union Blocks @resolveEditorBlockType = Text | Figure | Columns
|
|
16
|
+
|
|
17
|
+
union ColumnBlocks @resolveEditorBlockType = Text | Figure
|
|
18
|
+
|
|
19
|
+
type Text @type(id: "core/paragraph") {
|
|
20
|
+
content: String @resolveEditorBlockMarkup
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
type Figure @type(id: "custom/figure") {
|
|
24
|
+
caption: String @resolveEditorBlockAttribute(key: "caption")
|
|
25
|
+
image: Image @resolveEditorBlockMedia
|
|
26
|
+
imageAlt: String
|
|
27
|
+
@resolveEditorBlockMedia
|
|
28
|
+
@property(path: "field_media_image.alt")
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
type Image {
|
|
32
|
+
alt: String @property(path: "field_media_image.alt")
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
type Columns @type(id: "custom/columns") {
|
|
36
|
+
columns: [ColumnBlocks]! @resolveEditorBlockChildren
|
|
37
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
name: Silverback Gutenberg Test Validator
|
|
2
|
+
type: module
|
|
3
|
+
description:
|
|
4
|
+
'Support module for Silverback Gutenberg validators plugin system testing.'
|
|
5
|
+
package: 'Silverback Testing'
|
|
6
|
+
core_version_requirement: ^8 || ^9 || ^10 || ^11
|
|
7
|
+
|
|
8
|
+
dependencies:
|
|
9
|
+
- silverback_gutenberg:silverback_gutenberg
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Drupal\silverback_gutenberg_test_validator\Plugin\Validation\GutenbergValidator;
|
|
4
|
+
|
|
5
|
+
use Drupal\silverback_gutenberg\GutenbergValidation\GutenbergCardinalityValidatorInterface;
|
|
6
|
+
use Drupal\silverback_gutenberg\GutenbergValidation\GutenbergCardinalityValidatorTrait;
|
|
7
|
+
use Drupal\silverback_gutenberg\GutenbergValidation\GutenbergValidatorBase;
|
|
8
|
+
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Validate test plugin for the ANY cardinality.
|
|
12
|
+
*
|
|
13
|
+
* @GutenbergValidator(
|
|
14
|
+
* id="column_validator",
|
|
15
|
+
* label = @Translation("Column validator"),
|
|
16
|
+
* )
|
|
17
|
+
*/
|
|
18
|
+
class ColumnValidator extends GutenbergValidatorBase {
|
|
19
|
+
|
|
20
|
+
use GutenbergCardinalityValidatorTrait;
|
|
21
|
+
use StringTranslationTrait;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* {@inheritDoc}
|
|
25
|
+
*/
|
|
26
|
+
public function applies(array $block): bool {
|
|
27
|
+
return $block['blockName'] === 'core/column';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* {@inheritDoc}
|
|
32
|
+
*/
|
|
33
|
+
public function validateContent(array $block = []): array {
|
|
34
|
+
$expectedChildren = [
|
|
35
|
+
'validationType' => GutenbergCardinalityValidatorInterface::CARDINALITY_ANY,
|
|
36
|
+
'min' => 1,
|
|
37
|
+
'max' => 2,
|
|
38
|
+
];
|
|
39
|
+
return $this->validateCardinality($block, $expectedChildren);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Drupal\silverback_gutenberg_test_validator\Plugin\Validation\GutenbergValidator;
|
|
4
|
+
|
|
5
|
+
use Drupal\silverback_gutenberg\GutenbergValidation\GutenbergCardinalityValidatorTrait;
|
|
6
|
+
use Drupal\silverback_gutenberg\GutenbergValidation\GutenbergValidatorBase;
|
|
7
|
+
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Validate test plugin for multiple types of inner blocks.
|
|
11
|
+
*
|
|
12
|
+
* @GutenbergValidator(
|
|
13
|
+
* id="group_validator",
|
|
14
|
+
* label = @Translation("Group validator"),
|
|
15
|
+
* )
|
|
16
|
+
*/
|
|
17
|
+
class GroupValidator extends GutenbergValidatorBase {
|
|
18
|
+
|
|
19
|
+
use GutenbergCardinalityValidatorTrait;
|
|
20
|
+
use StringTranslationTrait;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* {@inheritDoc}
|
|
24
|
+
*/
|
|
25
|
+
public function applies(array $block): bool {
|
|
26
|
+
return $block['blockName'] === 'core/group';
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* {@inheritDoc}
|
|
31
|
+
*/
|
|
32
|
+
public function validateContent(array $block = []): array {
|
|
33
|
+
$expectedChildren = [
|
|
34
|
+
[
|
|
35
|
+
'blockName' => 'core/paragraph',
|
|
36
|
+
'blockLabel' => $this->t('Paragraph'),
|
|
37
|
+
'min' => 1,
|
|
38
|
+
'max' => 2,
|
|
39
|
+
],
|
|
40
|
+
[
|
|
41
|
+
'blockName' => 'core/list',
|
|
42
|
+
'blockLabel' => $this->t('List'),
|
|
43
|
+
'min' => 1,
|
|
44
|
+
'max' => 1,
|
|
45
|
+
],
|
|
46
|
+
];
|
|
47
|
+
return $this->validateCardinality($block, $expectedChildren);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Drupal\silverback_gutenberg_test_validator\Plugin\Validation\GutenbergValidator;
|
|
4
|
+
|
|
5
|
+
use Drupal\silverback_gutenberg\GutenbergValidation\GutenbergValidatorBase;
|
|
6
|
+
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Validate test plugin to check if a linkUrl and linkLabel attribute exists.
|
|
10
|
+
*
|
|
11
|
+
* @GutenbergValidator(
|
|
12
|
+
* id="link_validator",
|
|
13
|
+
* label = @Translation("Link URL"),
|
|
14
|
+
* )
|
|
15
|
+
*/
|
|
16
|
+
class LinkValidator extends GutenbergValidatorBase {
|
|
17
|
+
|
|
18
|
+
use StringTranslationTrait;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* {@inheritDoc}
|
|
22
|
+
*/
|
|
23
|
+
public function applies(array $block): bool {
|
|
24
|
+
return $block['blockName'] === 'custom/link';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* {@inheritDoc}
|
|
29
|
+
*/
|
|
30
|
+
public function validatedFields($block = []): array {
|
|
31
|
+
return [
|
|
32
|
+
'linkUrl' => [
|
|
33
|
+
'field_label' => $this->t('Link URL'),
|
|
34
|
+
'rules' => ['required']
|
|
35
|
+
],
|
|
36
|
+
'linkLabel' => [
|
|
37
|
+
'field_label' => $this->t('Link Label'),
|
|
38
|
+
'rules' => ['required']
|
|
39
|
+
],
|
|
40
|
+
];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Drupal\Tests\silverback_gutenberg\Kernel;
|
|
4
|
+
|
|
5
|
+
use Drupal\KernelTests\KernelTestBase;
|
|
6
|
+
use Drupal\node\Entity\Node;
|
|
7
|
+
use Drupal\silverback_gutenberg\Plugin\Validation\Constraint\Gutenberg;
|
|
8
|
+
use Drupal\silverback_gutenberg\Plugin\Validation\Constraint\GutenbergValidator;
|
|
9
|
+
use Drupal\Tests\node\Traits\ContentTypeCreationTrait;
|
|
10
|
+
use Drupal\Tests\silverback_gutenberg\Traits\SampleAssetTrait;
|
|
11
|
+
use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
|
12
|
+
|
|
13
|
+
class BlockValidationRuleTest extends KernelTestBase {
|
|
14
|
+
|
|
15
|
+
use ContentTypeCreationTrait;
|
|
16
|
+
use SampleAssetTrait;
|
|
17
|
+
|
|
18
|
+
const FIELD_NAME = 'field_test';
|
|
19
|
+
|
|
20
|
+
protected static $modules = [
|
|
21
|
+
'system',
|
|
22
|
+
'path_alias',
|
|
23
|
+
'field',
|
|
24
|
+
'node',
|
|
25
|
+
'user',
|
|
26
|
+
'text',
|
|
27
|
+
'silverback_gutenberg',
|
|
28
|
+
'silverback_gutenberg_test_validator',
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @var \Drupal\silverback_gutenberg\GutenbergValidation\GutenbergValidatorRuleManager
|
|
33
|
+
*/
|
|
34
|
+
protected $validatorRulePluginManager;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @var \Drupal\silverback_gutenberg\GutenbergValidation\GutenbergValidatorManager
|
|
38
|
+
*/
|
|
39
|
+
protected $validatorPluginManager;
|
|
40
|
+
|
|
41
|
+
protected function setUp(): void {
|
|
42
|
+
parent::setUp();
|
|
43
|
+
|
|
44
|
+
$this->installSchema('system', 'sequences');
|
|
45
|
+
$this->installEntitySchema('node');
|
|
46
|
+
$this->installEntitySchema('user');
|
|
47
|
+
$this->installConfig('node');
|
|
48
|
+
$this->createContentType([
|
|
49
|
+
'type' => 'page',
|
|
50
|
+
'name' => 'Basic page'
|
|
51
|
+
]);
|
|
52
|
+
|
|
53
|
+
$this->validatorRulePluginManager = \Drupal::service('plugin.manager.gutenberg_validator_rule');
|
|
54
|
+
$this->validatorPluginManager = \Drupal::service('plugin.manager.gutenberg_validator');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
public function testEmailValidatorRule(): void {
|
|
58
|
+
/** @var \Drupal\silverback_gutenberg\GutenbergValidation\GutenbergValidatorRuleInterface $emailValidator */
|
|
59
|
+
$emailValidator = $this->validatorRulePluginManager->createInstance('email');
|
|
60
|
+
$this->assertTrue($emailValidator->validate('test@example', self::FIELD_NAME));
|
|
61
|
+
$this->assertTrue($emailValidator->validate('test@example.com', self::FIELD_NAME));
|
|
62
|
+
$this->assertEquals($emailValidator->validate('test@', self::FIELD_NAME), '<em class="placeholder">' . self::FIELD_NAME . '</em> is not valid.');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
public function testRequiredValidatorRule(): void {
|
|
66
|
+
$requiredMessage = '<em class="placeholder">' . self::FIELD_NAME . '</em> field is required.';
|
|
67
|
+
/** @var \Drupal\silverback_gutenberg\GutenbergValidation\GutenbergValidatorRuleInterface $requiredValidator */
|
|
68
|
+
$requiredValidator = $this->validatorRulePluginManager->createInstance('required');
|
|
69
|
+
$this->assertTrue($requiredValidator->validate('banana', self::FIELD_NAME));
|
|
70
|
+
$this->assertEquals($requiredValidator->validate(NULL, self::FIELD_NAME), $requiredMessage);
|
|
71
|
+
$this->assertEquals($requiredValidator->validate('', self::FIELD_NAME), $requiredMessage);
|
|
72
|
+
$this->assertEquals($requiredValidator->validate('_none', self::FIELD_NAME), $requiredMessage);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Limit test to content validator only.
|
|
77
|
+
*
|
|
78
|
+
* More specific cardinality tests are covered by BlocksValidatorCardinalityTest.
|
|
79
|
+
*
|
|
80
|
+
* @return void
|
|
81
|
+
*/
|
|
82
|
+
public function testContentValidator(): void {
|
|
83
|
+
// Validates any blocks.
|
|
84
|
+
/** @var \Drupal\silverback_gutenberg\GutenbergValidation\GutenbergValidatorInterface $columnValidator */
|
|
85
|
+
$columnValidator = $this->validatorPluginManager->createInstance('column_validator');
|
|
86
|
+
$columnBlock = [
|
|
87
|
+
'blockName' => 'core/column',
|
|
88
|
+
'innerBlocks' => [
|
|
89
|
+
[
|
|
90
|
+
'blockName' => 'core/paragraph',
|
|
91
|
+
],
|
|
92
|
+
[
|
|
93
|
+
'blockName' => 'core/list',
|
|
94
|
+
],
|
|
95
|
+
],
|
|
96
|
+
];
|
|
97
|
+
$this->assertEquals($columnValidator->validateContent($columnBlock), ['is_valid' => TRUE, 'message' => '']);
|
|
98
|
+
|
|
99
|
+
// Validates multiple specific blocks.
|
|
100
|
+
$groupValidator = $this->validatorPluginManager->createInstance('group_validator');
|
|
101
|
+
$groupBlock = [
|
|
102
|
+
'blockName' => 'core/group',
|
|
103
|
+
'innerBlocks' => [
|
|
104
|
+
[
|
|
105
|
+
'blockName' => 'core/paragraph',
|
|
106
|
+
],
|
|
107
|
+
[
|
|
108
|
+
'blockName' => 'core/paragraph',
|
|
109
|
+
],
|
|
110
|
+
[
|
|
111
|
+
'blockName' => 'core/list',
|
|
112
|
+
],
|
|
113
|
+
],
|
|
114
|
+
];
|
|
115
|
+
$this->assertEquals($groupValidator->validateContent($groupBlock), ['is_valid' => TRUE, 'message' => '']);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* @dataProvider providerTestFieldValidator
|
|
120
|
+
*/
|
|
121
|
+
public function testFieldValidator(string $body, array $expected_violations): void {
|
|
122
|
+
$context = $this->prophesize(ExecutionContextInterface::class);
|
|
123
|
+
$constraintValidator = new GutenbergValidator($this->validatorPluginManager, $this->validatorRulePluginManager);
|
|
124
|
+
$constraintValidator->initialize($context->reveal());
|
|
125
|
+
|
|
126
|
+
$node = Node::create([
|
|
127
|
+
'type' => 'page',
|
|
128
|
+
'title' => 'Test',
|
|
129
|
+
'body' => $body,
|
|
130
|
+
]);
|
|
131
|
+
$node->save();
|
|
132
|
+
$constraintValidator->validate($node->get('body'), new Gutenberg());
|
|
133
|
+
$violations = $this->getViolations($constraintValidator);
|
|
134
|
+
$this->assertEquals($violations, $expected_violations);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Data provider for ::testFieldValidator().
|
|
139
|
+
*/
|
|
140
|
+
public function providerTestFieldValidator() {
|
|
141
|
+
return [
|
|
142
|
+
[
|
|
143
|
+
'body' => '<!-- wp:custom/link {"linkLabel":"test","linkUrl":"https://example.com"} /-->',
|
|
144
|
+
'violations' => [],
|
|
145
|
+
],
|
|
146
|
+
[
|
|
147
|
+
'body' => '<!-- wp:custom/link {"linkLabel":"test","linkUrl":""} /-->',
|
|
148
|
+
'violations' => [
|
|
149
|
+
[
|
|
150
|
+
'attribute' => 'linkUrl',
|
|
151
|
+
'blockName' => 'custom/link',
|
|
152
|
+
'rule' => 'required',
|
|
153
|
+
'message' => '<span class="block-validation-error" data-block-instance="1" data-block-type="custom/link">Link: <em class="placeholder">Link URL</em> field is required.</span>',
|
|
154
|
+
],
|
|
155
|
+
]
|
|
156
|
+
],
|
|
157
|
+
[
|
|
158
|
+
'body' => '<!-- wp:custom/link {"linkLabel":"","linkUrl":"https://example.com"} /-->',
|
|
159
|
+
'violations' => [
|
|
160
|
+
[
|
|
161
|
+
'attribute' => 'linkLabel',
|
|
162
|
+
'blockName' => 'custom/link',
|
|
163
|
+
'rule' => 'required',
|
|
164
|
+
'message' => '<span class="block-validation-error" data-block-instance="1" data-block-type="custom/link">Link: <em class="placeholder">Link Label</em> field is required.</span>'
|
|
165
|
+
],
|
|
166
|
+
]
|
|
167
|
+
],
|
|
168
|
+
[
|
|
169
|
+
'body' => '<!-- wp:custom/link {"linkLabel":"","linkUrl":""} /-->',
|
|
170
|
+
'violations' => [
|
|
171
|
+
[
|
|
172
|
+
'attribute' => 'linkUrl',
|
|
173
|
+
'blockName' => 'custom/link',
|
|
174
|
+
'rule' => 'required',
|
|
175
|
+
'message' => '<span class="block-validation-error" data-block-instance="1" data-block-type="custom/link">Link: <em class="placeholder">Link URL</em> field is required.</span>',
|
|
176
|
+
],
|
|
177
|
+
[
|
|
178
|
+
'attribute' => 'linkLabel',
|
|
179
|
+
'blockName' => 'custom/link',
|
|
180
|
+
'rule' => 'required',
|
|
181
|
+
'message' => '<span class="block-validation-error" data-block-instance="1" data-block-type="custom/link">Link: <em class="placeholder">Link Label</em> field is required.</span>'
|
|
182
|
+
],
|
|
183
|
+
]
|
|
184
|
+
]
|
|
185
|
+
];
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
private function getViolations(GutenbergValidator $validator) {
|
|
189
|
+
$reflection = new \ReflectionClass($validator);
|
|
190
|
+
$property = $reflection->getProperty('violations');
|
|
191
|
+
return $property->getValue($validator);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
}
|