@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,114 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Drupal\silverback_gutenberg\BlockMutator;
|
|
4
|
+
|
|
5
|
+
use Drupal\Core\Cache\CacheBackendInterface;
|
|
6
|
+
use Drupal\Core\Extension\ModuleHandlerInterface;
|
|
7
|
+
use Drupal\Core\Plugin\DefaultPluginManager;
|
|
8
|
+
use Drupal\silverback_gutenberg\BlockMutator\BlockMutatorInterface;
|
|
9
|
+
use Drupal\silverback_gutenberg\Attribute\GutenbergBlockMutator;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The block mutator plugin manager class.
|
|
13
|
+
*/
|
|
14
|
+
class BlockMutatorManager extends DefaultPluginManager implements BlockMutatorManagerInterface {
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Constructs a BlockMutatorManager object.
|
|
18
|
+
*
|
|
19
|
+
* @param \Traversable $namespaces
|
|
20
|
+
* An object that implements \Traversable which contains the root paths
|
|
21
|
+
* keyed by the corresponding namespace to look for plugin implementations.
|
|
22
|
+
* @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
|
|
23
|
+
* Cache backend instance to use.
|
|
24
|
+
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
|
25
|
+
* The module handler to invoke the alter hook with.
|
|
26
|
+
*/
|
|
27
|
+
public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) {
|
|
28
|
+
parent::__construct(
|
|
29
|
+
'Plugin/GutenbergBlockMutator',
|
|
30
|
+
$namespaces,
|
|
31
|
+
$module_handler,
|
|
32
|
+
BlockMutatorInterface::class,
|
|
33
|
+
GutenbergBlockMutator::class,
|
|
34
|
+
// Keeping BC for deprecated Annotation
|
|
35
|
+
'Drupal\silverback_gutenberg\Annotation\GutenbergBlockMutator',
|
|
36
|
+
);
|
|
37
|
+
$this->alterInfo('gutenberg_block_mutator_info');
|
|
38
|
+
$this->setCacheBackend($cache_backend, 'gutenberg_block_mutator_info_plugins');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* {@inheritDoc}
|
|
43
|
+
*/
|
|
44
|
+
public function mutateExport(array &$blocks, array &$dependencies): void {
|
|
45
|
+
$this->mutate('export', $blocks, $dependencies);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* {@inheritDoc}
|
|
50
|
+
*/
|
|
51
|
+
public function mutateImport(array &$blocks): void {
|
|
52
|
+
$this->mutate('import', $blocks);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Mutates a set of gutenberg blocks into a specicif direction
|
|
57
|
+
* (import/export).
|
|
58
|
+
*
|
|
59
|
+
* @param string $direction
|
|
60
|
+
* The direction of the mutation. Can be "import" or "export".
|
|
61
|
+
* @param array $blocks
|
|
62
|
+
* The gutenberg blocks being processed.
|
|
63
|
+
* @param array $dependencies
|
|
64
|
+
* An array of dependencies for these blocks. Each key of the array should be
|
|
65
|
+
* a uuid and the value should be the corresponding entity type. Example:
|
|
66
|
+
* $dependencies['some-uuid'] = 'media';
|
|
67
|
+
*/
|
|
68
|
+
protected function mutate(string $direction, array &$blocks, array &$dependencies = []): void {
|
|
69
|
+
if (empty($blocks)) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
foreach ($this->getDefinitions() as $definition) {
|
|
73
|
+
/* @var \Drupal\silverback_gutenberg\BlockMutator\BlockMutatorInterface $mutatorPlugin */
|
|
74
|
+
$mutatorPlugin = $this->createInstance($definition['id']);
|
|
75
|
+
$this->doMutate($mutatorPlugin, $direction, $blocks, $dependencies);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Performs a mutation recursively on a set of gutenberg blocks, using a
|
|
81
|
+
* specific mutator plugin, into a specific direction (import / export).
|
|
82
|
+
*
|
|
83
|
+
* @param BlockMutatorInterface $mutatorPlugin
|
|
84
|
+
* A block mutator plugin.
|
|
85
|
+
* @param string $direction
|
|
86
|
+
* The direction of the mutation. Can be "import" or "export". If an invalid
|
|
87
|
+
* string is sent, the "export" value is used.
|
|
88
|
+
* @param array $blocks
|
|
89
|
+
* The gutenberg blocks being processed.
|
|
90
|
+
* @param array $dependencies
|
|
91
|
+
* An array of dependencies for these blocks. Each key of the array should be
|
|
92
|
+
* a uuid and the value should be the corresponding entity type. Example:
|
|
93
|
+
* $dependencies['some-uuid'] = 'media';
|
|
94
|
+
*/
|
|
95
|
+
protected function doMutate(BlockMutatorInterface $mutatorPlugin, string $direction, array &$blocks, array &$dependencies = []): void {
|
|
96
|
+
foreach ($blocks as &$block) {
|
|
97
|
+
if ($mutatorPlugin->applies($block)) {
|
|
98
|
+
switch ($direction) {
|
|
99
|
+
case 'import':
|
|
100
|
+
$mutatorPlugin->mutateImport($block);
|
|
101
|
+
break;
|
|
102
|
+
case 'export':
|
|
103
|
+
default:
|
|
104
|
+
$mutatorPlugin->mutateExport($block, $dependencies);
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (!empty($block['innerBlocks'])) {
|
|
109
|
+
$this->doMutate($mutatorPlugin, $direction, $block['innerBlocks'], $dependencies);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Drupal\silverback_gutenberg\BlockMutator;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Interface for the block mutator plugin manager.
|
|
7
|
+
*/
|
|
8
|
+
interface BlockMutatorManagerInterface {
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Mutates the gutenberg blocks before they are exported by the default
|
|
12
|
+
* content module.
|
|
13
|
+
*
|
|
14
|
+
* @param array $blocks
|
|
15
|
+
* The gutenberg blocks being processed.
|
|
16
|
+
* @param array $dependencies
|
|
17
|
+
* An array of dependencies for these blocks. Each key of the array should be
|
|
18
|
+
* a uuid and the value should be the corresponding entity type. Example:
|
|
19
|
+
* $dependencies['some-uuid'] = 'media';
|
|
20
|
+
*/
|
|
21
|
+
public function mutateExport(array &$blocks, array &$dependencies): void;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Mutates the gutenberg blocks before they get imported.
|
|
25
|
+
*
|
|
26
|
+
* @param array $blocks
|
|
27
|
+
* The gutenberg blocks being processed.
|
|
28
|
+
*/
|
|
29
|
+
public function mutateImport(array &$blocks): void;
|
|
30
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Drupal\silverback_gutenberg\BlockMutator;
|
|
4
|
+
|
|
5
|
+
use Drupal\Core\Entity\ContentEntityInterface;
|
|
6
|
+
use Drupal\Core\Entity\EntityRepositoryInterface;
|
|
7
|
+
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
|
8
|
+
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
|
|
9
|
+
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
|
10
|
+
use Drupal\Core\Plugin\PluginBase;
|
|
11
|
+
use Symfony\Component\DependencyInjection\ContainerInterface;
|
|
12
|
+
|
|
13
|
+
abstract class EntityBlockMutatorBase extends PluginBase implements
|
|
14
|
+
BlockMutatorInterface,
|
|
15
|
+
ContainerFactoryPluginInterface {
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The Gutenberg attribute id.
|
|
19
|
+
*
|
|
20
|
+
* @var string $gutenbergAttribute
|
|
21
|
+
*/
|
|
22
|
+
public string $gutenbergAttribute;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Indicates if the Gutenberg attribute is a collection or a single value.
|
|
26
|
+
*
|
|
27
|
+
* @var bool $isMultiple
|
|
28
|
+
*/
|
|
29
|
+
public bool $isMultiple = FALSE;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* The Drupal entity type id.
|
|
33
|
+
*
|
|
34
|
+
* @var string $entityTypeId
|
|
35
|
+
*/
|
|
36
|
+
public string $entityTypeId;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* EntityBlockMutatorBase constructor.
|
|
40
|
+
*
|
|
41
|
+
* @param \Drupal\Core\Entity\EntityRepositoryInterface $repository
|
|
42
|
+
*/
|
|
43
|
+
public function __construct(
|
|
44
|
+
array $configuration,
|
|
45
|
+
$plugin_id,
|
|
46
|
+
$plugin_definition,
|
|
47
|
+
private readonly EntityRepositoryInterface $entityRepository,
|
|
48
|
+
private readonly EntityTypeManagerInterface $entityTypeManager,
|
|
49
|
+
private readonly LoggerChannelFactoryInterface $loggerFactory,
|
|
50
|
+
) {
|
|
51
|
+
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* {@inheritDoc}
|
|
56
|
+
*/
|
|
57
|
+
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
|
|
58
|
+
return new static(
|
|
59
|
+
$configuration,
|
|
60
|
+
$plugin_id,
|
|
61
|
+
$plugin_definition,
|
|
62
|
+
$container->get('entity.repository'),
|
|
63
|
+
$container->get('entity_type.manager'),
|
|
64
|
+
$container->get('logger.factory'),
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* {@inheritDoc}
|
|
70
|
+
*/
|
|
71
|
+
public function applies(array $block) : bool {
|
|
72
|
+
if (empty($this->gutenbergAttribute)) {
|
|
73
|
+
$this->loggerFactory->get('silverback_gutenberg')->warning(
|
|
74
|
+
$this->t('Block mutator attribute is not set for @class, ignoring.', [
|
|
75
|
+
'@class' => self::class,
|
|
76
|
+
])
|
|
77
|
+
);
|
|
78
|
+
return FALSE;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (empty($this->entityTypeId)) {
|
|
82
|
+
$this->loggerFactory->get('silverback_gutenberg')->warning(
|
|
83
|
+
$this->t('Block mutator attribute @attribute does not specifiy the entity type id for @class, ignoring.', [
|
|
84
|
+
'@attribute' => $this->gutenbergAttribute,
|
|
85
|
+
'@class' => self::class,
|
|
86
|
+
])
|
|
87
|
+
);
|
|
88
|
+
return FALSE;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
$entityTypes = $this->entityTypeManager->getDefinitions();
|
|
92
|
+
if (!array_key_exists($this->entityTypeId, $entityTypes)) {
|
|
93
|
+
throw new \Exception(
|
|
94
|
+
$this->t('Block mutator attribute @attribute does not have a valid entity type @entity_type_id for @class, ignoring.', [
|
|
95
|
+
'@attribute' => $this->gutenbergAttribute,
|
|
96
|
+
'@entity_type_id' => $this->entityTypeId,
|
|
97
|
+
'@class' => self::class,
|
|
98
|
+
])
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (empty($block['attrs'][$this->gutenbergAttribute])) {
|
|
103
|
+
return FALSE;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (
|
|
107
|
+
$this->isMultiple &&
|
|
108
|
+
!is_array($block['attrs'][$this->gutenbergAttribute])
|
|
109
|
+
) {
|
|
110
|
+
throw new \Exception(
|
|
111
|
+
$this->t('Block mutator attribute @attribute is set to be multiple but is not iterable.', [
|
|
112
|
+
'@attribute' => $this->gutenbergAttribute,
|
|
113
|
+
])
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return TRUE;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* {@inheritDoc}
|
|
122
|
+
*/
|
|
123
|
+
public function mutateExport(array &$block, array &$dependencies) : void {
|
|
124
|
+
if ($this->isMultiple) {
|
|
125
|
+
$block['attrs'][$this->gutenbergAttribute] = array_values(array_map(
|
|
126
|
+
function (ContentEntityInterface $entity) use (&$dependencies) {
|
|
127
|
+
$dependencies[$entity->uuid()] = $this->entityTypeId;
|
|
128
|
+
return $entity->uuid();
|
|
129
|
+
},
|
|
130
|
+
$this->entityRepository
|
|
131
|
+
->getCanonicalMultiple($this->entityTypeId, $block['attrs'][$this->gutenbergAttribute])
|
|
132
|
+
));
|
|
133
|
+
} else {
|
|
134
|
+
$entity = $this->entityRepository->getCanonical($this->entityTypeId, $block['attrs'][$this->gutenbergAttribute]);
|
|
135
|
+
if (!$entity instanceof ContentEntityInterface) {
|
|
136
|
+
$block['attrs'][$this->gutenbergAttribute] = '';
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
$block['attrs'][$this->gutenbergAttribute] = $entity->uuid();
|
|
140
|
+
$dependencies[$entity->uuid()] = $this->entityTypeId;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* {@inheritDoc}
|
|
146
|
+
*/
|
|
147
|
+
public function mutateImport(array &$block) : void {
|
|
148
|
+
if ($this->isMultiple) {
|
|
149
|
+
$block['attrs'][$this->gutenbergAttribute] = array_map(
|
|
150
|
+
function (string $uuid) {
|
|
151
|
+
try {
|
|
152
|
+
$entity = $this->entityRepository->loadEntityByUuid($this->entityTypeId, $uuid);
|
|
153
|
+
return $entity->id();
|
|
154
|
+
}
|
|
155
|
+
catch (\Throwable $e) {
|
|
156
|
+
$this->loggerFactory->get('silverback_gutenberg')->warning(
|
|
157
|
+
$this->t(
|
|
158
|
+
'@class: Could not load @entity_type_id by uuid @uuid on import.', [
|
|
159
|
+
'@class' => self::class,
|
|
160
|
+
'@entity_type_id' => $this->entityTypeId,
|
|
161
|
+
'@uuid' => $uuid,
|
|
162
|
+
]
|
|
163
|
+
)
|
|
164
|
+
);
|
|
165
|
+
return $uuid;
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
$block['attrs'][$this->gutenbergAttribute]
|
|
169
|
+
);
|
|
170
|
+
} else {
|
|
171
|
+
try {
|
|
172
|
+
$entity = $this->entityRepository->loadEntityByUuid($this->entityTypeId, $block['attrs'][$this->gutenbergAttribute]);
|
|
173
|
+
$block['attrs'][$this->gutenbergAttribute] = $entity->id();
|
|
174
|
+
}
|
|
175
|
+
catch (\Throwable $e) {
|
|
176
|
+
$this->loggerFactory->get('silverback_gutenberg')->warning(
|
|
177
|
+
$this->t(
|
|
178
|
+
'@class: Could not load @entity_type_id by uuid @uuid on import.', [
|
|
179
|
+
'@class' => self::class,
|
|
180
|
+
'@entity_type_id' => $this->entityTypeId,
|
|
181
|
+
'@uuid' => $uuid,
|
|
182
|
+
]
|
|
183
|
+
)
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Drupal\silverback_gutenberg;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Class BlockSerializer
|
|
7
|
+
*
|
|
8
|
+
* Copied from:
|
|
9
|
+
* https://core.trac.wordpress.org/browser/tags/5.3.1/src/wp-includes/blocks.php#L226
|
|
10
|
+
*
|
|
11
|
+
* @package Drupal\silverback_gutenberg
|
|
12
|
+
*/
|
|
13
|
+
class BlockSerializer {
|
|
14
|
+
|
|
15
|
+
public function serialize_blocks( $blocks ) {
|
|
16
|
+
return implode( '', array_map( fn ($block) => $this->serialize_block($block), $blocks ) );
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
protected function serialize_block( $block ) {
|
|
20
|
+
$block_content = '';
|
|
21
|
+
|
|
22
|
+
$index = 0;
|
|
23
|
+
foreach ( $block['innerContent'] as $chunk ) {
|
|
24
|
+
$block_content .= is_string( $chunk ) ? $chunk : $this->serialize_block( $block['innerBlocks'][ $index++ ] );
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if ( ! is_array( $block['attrs'] ) ) {
|
|
28
|
+
$block['attrs'] = array();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return $this->get_comment_delimited_block_content(
|
|
32
|
+
$block['blockName'],
|
|
33
|
+
$block['attrs'],
|
|
34
|
+
$block_content
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
protected function get_comment_delimited_block_content($block_name = null, $block_attributes = null, $block_content = '') {
|
|
39
|
+
if ( is_null( $block_name ) ) {
|
|
40
|
+
return $block_content;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
$serialized_block_name = $this->strip_core_block_namespace(
|
|
44
|
+
$block_name
|
|
45
|
+
);
|
|
46
|
+
$serialized_attributes = empty( $block_attributes ) ? '' : $this->serialize_block_attributes(
|
|
47
|
+
$block_attributes
|
|
48
|
+
) . ' ';
|
|
49
|
+
|
|
50
|
+
if ( empty( $block_content ) ) {
|
|
51
|
+
return sprintf( '<!-- wp:%s %s/-->', $serialized_block_name, $serialized_attributes );
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return sprintf(
|
|
55
|
+
'<!-- wp:%s %s-->%s<!-- /wp:%s -->',
|
|
56
|
+
$serialized_block_name,
|
|
57
|
+
$serialized_attributes,
|
|
58
|
+
$block_content,
|
|
59
|
+
$serialized_block_name
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
protected function strip_core_block_namespace( $block_name = null ) {
|
|
64
|
+
if ( is_string( $block_name ) && 0 === strpos( $block_name, 'core/' ) ) {
|
|
65
|
+
return substr( $block_name, 5 );
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return $block_name;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
protected function serialize_block_attributes( $block_attributes ) {
|
|
73
|
+
// TODO: Original wordpress serializer does not unescape slashes.
|
|
74
|
+
$encoded_attributes = json_encode( $block_attributes, JSON_UNESCAPED_SLASHES );
|
|
75
|
+
$encoded_attributes = preg_replace( '/--/', '\\u002d\\u002d', $encoded_attributes );
|
|
76
|
+
$encoded_attributes = preg_replace( '/</', '\\u003c', $encoded_attributes );
|
|
77
|
+
$encoded_attributes = preg_replace( '/>/', '\\u003e', $encoded_attributes );
|
|
78
|
+
$encoded_attributes = preg_replace( '/&/', '\\u0026', $encoded_attributes );
|
|
79
|
+
// Regex: /\\"/
|
|
80
|
+
$encoded_attributes = preg_replace( '/\\\\"/', '\\u0022', $encoded_attributes );
|
|
81
|
+
|
|
82
|
+
return $encoded_attributes;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Drupal\silverback_gutenberg\Controller;
|
|
4
|
+
|
|
5
|
+
use Drupal\Component\Serialization\Json;
|
|
6
|
+
use Drupal\Core\Entity\EntityInterface;
|
|
7
|
+
use Drupal\gutenberg\Controller\SearchController;
|
|
8
|
+
use Drupal\linkit\Controller\AutocompleteController;
|
|
9
|
+
use Symfony\Component\HttpFoundation\JsonResponse;
|
|
10
|
+
use Symfony\Component\HttpFoundation\Request;
|
|
11
|
+
|
|
12
|
+
class LinkitAutocomplete extends SearchController {
|
|
13
|
+
|
|
14
|
+
public function search(Request $request): JsonResponse {
|
|
15
|
+
// As we're limited on changes we can make to the Gutenberg plugin, we're
|
|
16
|
+
// using the subtype to identify the linkit profile to use. With it defaulting
|
|
17
|
+
// to a linkit profile with id gutenberg. By default, it's set to the string "undefined", so we
|
|
18
|
+
// need to check for that as well.
|
|
19
|
+
$linkitProfileIdParam = $request->query->get('subtype');
|
|
20
|
+
$linkitProfileId = $linkitProfileIdParam !== "undefined" && $linkitProfileIdParam ? $linkitProfileIdParam : 'gutenberg';
|
|
21
|
+
|
|
22
|
+
if (
|
|
23
|
+
!\Drupal::moduleHandler()->moduleExists('linkit') ||
|
|
24
|
+
!($linkitProfile = \Drupal::entityTypeManager()->getStorage('linkit_profile')->load($linkitProfileId))
|
|
25
|
+
) {
|
|
26
|
+
return parent::search($request);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
$type = (string) $request->query->get('type');
|
|
30
|
+
if ($type !== 'post') {
|
|
31
|
+
return new JsonResponse([]);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
$result = [];
|
|
35
|
+
$search = (string) $request->query->get('search');
|
|
36
|
+
$request->query->set('q', $search);
|
|
37
|
+
$linkitController = AutocompleteController::create(\Drupal::getContainer());
|
|
38
|
+
$response = $linkitController->autocomplete($request, $linkitProfile);
|
|
39
|
+
$rows = Json::decode($response->getContent())['suggestions'] ?? [];
|
|
40
|
+
/** @var \Drupal\linkit\SubstitutionManagerInterface $substitutionManager */
|
|
41
|
+
$substitutionManager = \Drupal::service('plugin.manager.linkit.substitution');
|
|
42
|
+
/** @var \Drupal\Core\Entity\EntityRepositoryInterface $entityRepository */
|
|
43
|
+
$entityRepository = \Drupal::service('entity.repository');
|
|
44
|
+
$langcode = \Drupal::languageManager()->getCurrentLanguage()->getId();
|
|
45
|
+
foreach ($rows as $row) {
|
|
46
|
+
$url = NULL;
|
|
47
|
+
$entity = NULL;
|
|
48
|
+
if (isset($row['entity_type_id'], $row['entity_uuid'])) {
|
|
49
|
+
if ($entity = $entityRepository->loadEntityByUuid($row['entity_type_id'], $row['entity_uuid'])) {
|
|
50
|
+
$entity = $entityRepository->getTranslationFromContext($entity, $langcode);
|
|
51
|
+
if (isset($row['substitution_id'])) {
|
|
52
|
+
$url = $substitutionManager
|
|
53
|
+
->createInstance($row['substitution_id'])
|
|
54
|
+
->getUrl($entity)
|
|
55
|
+
->toString();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
$result[] = [
|
|
60
|
+
'id' => $row['entity_uuid'],
|
|
61
|
+
'title' => $row['label'],
|
|
62
|
+
'type' => $entity
|
|
63
|
+
? $this->getBundle($entity)
|
|
64
|
+
: ($row['entity_type_id'] ?? $row['description'] ?? NULL),
|
|
65
|
+
'url' => $url ?? $row['path'],
|
|
66
|
+
];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return new JsonResponse($result);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
protected function getBundle(EntityInterface $entity): string {
|
|
73
|
+
$entityType = $entity->getEntityType();
|
|
74
|
+
$bundle = NULL;
|
|
75
|
+
if ($entityType->hasKey('bundle')) {
|
|
76
|
+
$bundleKey = $entityType->getKey('bundle');
|
|
77
|
+
$bundle = $entity->get($bundleKey)->entity;
|
|
78
|
+
}
|
|
79
|
+
return $bundle
|
|
80
|
+
? "{$entityType->getLabel()}: {$bundle->label()}"
|
|
81
|
+
: "{$entityType->getLabel()}";
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Drupal\silverback_gutenberg;
|
|
4
|
+
|
|
5
|
+
use Drupal\Core\Entity\EntityInterface;
|
|
6
|
+
use Drupal\graphql\GraphQL\Resolver\ResolverInterface;
|
|
7
|
+
use Drupal\graphql\GraphQL\ResolverBuilder;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Custom directives for silverback compatibility.
|
|
11
|
+
*/
|
|
12
|
+
class Directives {
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Extract gutenberg blocks from a page.
|
|
16
|
+
*/
|
|
17
|
+
public static function editorBlocks(ResolverBuilder $builder) : ResolverInterface {
|
|
18
|
+
return $builder->produce('editor_blocks', [
|
|
19
|
+
'path' => $builder->fromArgument('path'),
|
|
20
|
+
'entity' => $builder->fromParent(),
|
|
21
|
+
'type' => $builder->compose(
|
|
22
|
+
$builder->fromParent(),
|
|
23
|
+
$builder->callback(
|
|
24
|
+
fn(EntityInterface $entity) =>
|
|
25
|
+
$entity->getTypedData()->getDataDefinition()->getDataType()
|
|
26
|
+
)),
|
|
27
|
+
'ignored' => $builder->fromArgument('ignored') ?? $builder->fromValue([]),
|
|
28
|
+
'aggregated' => $builder->fromArgument('aggregated') ?? $builder->fromValue(['core/paragraph']),
|
|
29
|
+
]);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Retrieve a blocks inner-blocks.
|
|
34
|
+
*/
|
|
35
|
+
public static function editorBlockChildren(ResolverBuilder $builder) : ResolverInterface {
|
|
36
|
+
return $builder->produce('editor_block_children')
|
|
37
|
+
->map('block', $builder->fromParent());
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Retrieve a blocks markup.
|
|
42
|
+
*/
|
|
43
|
+
public static function editorBlockMarkup(ResolverBuilder $builder) : ResolverInterface {
|
|
44
|
+
return $builder->produce('editor_block_html')
|
|
45
|
+
->map('block', $builder->fromParent());
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Retrieve a blocks media entity.
|
|
50
|
+
*/
|
|
51
|
+
public static function editorBlockMedia(ResolverBuilder $builder) : ResolverInterface {
|
|
52
|
+
return $builder->produce('editor_block_media')
|
|
53
|
+
->map('block', $builder->fromParent());
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Retrieve a blocks type.
|
|
58
|
+
*/
|
|
59
|
+
public static function editorBlockType(ResolverBuilder $builder) : ResolverInterface {
|
|
60
|
+
return $builder->produce('editor_block_type')
|
|
61
|
+
->map('block', $builder->fromParent());
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Extract an attribute from a gutenberg block.
|
|
66
|
+
*/
|
|
67
|
+
public static function editorBlockAttribute(ResolverBuilder $builder) : ResolverInterface {
|
|
68
|
+
return $builder->produce('editor_block_attribute')
|
|
69
|
+
->map('block', $builder->fromParent())
|
|
70
|
+
->map('name', $builder->fromArgument('key'))
|
|
71
|
+
->map('plainText', $builder->fromArgument('plainText') ?? $builder->fromValue(TRUE));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
namespace Drupal\silverback_gutenberg;
|
|
3
|
+
|
|
4
|
+
class EditorBlocksProcessor {
|
|
5
|
+
|
|
6
|
+
static function aggregateParagraphs(array $blocks, ?array $types = ['core/paragraph']) {
|
|
7
|
+
$processed = [];
|
|
8
|
+
$content = [];
|
|
9
|
+
foreach ($blocks as $block) {
|
|
10
|
+
if (in_array($block['blockName'], $types)) {
|
|
11
|
+
$content[] = $block['innerHTML'];
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
if (count($content)) {
|
|
15
|
+
$processed[] = [
|
|
16
|
+
'blockName' => 'core/paragraph',
|
|
17
|
+
'innerHTML' => implode('', $content),
|
|
18
|
+
];
|
|
19
|
+
$content = [];
|
|
20
|
+
}
|
|
21
|
+
if ($block['innerBlocks']) {
|
|
22
|
+
$block['innerBlocks'] = static::aggregateParagraphs($block['innerBlocks'], $types);
|
|
23
|
+
}
|
|
24
|
+
$processed[] = $block;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
if (count($content)) {
|
|
28
|
+
$processed[] = [
|
|
29
|
+
'blockName' => 'core/paragraph',
|
|
30
|
+
'innerHTML' => implode('', $content),
|
|
31
|
+
];
|
|
32
|
+
}
|
|
33
|
+
return $processed;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
static function processsIgnoredBlocks(array $blocks, ?array $ignored) {
|
|
37
|
+
if (empty($ignored)) {
|
|
38
|
+
return $blocks;
|
|
39
|
+
}
|
|
40
|
+
$processed = [];
|
|
41
|
+
foreach (array_filter($blocks, fn ($block) => !!$block['blockName']) as $block) {
|
|
42
|
+
if (in_array($block['blockName'], $ignored)) {
|
|
43
|
+
foreach ((static::processsIgnoredBlocks($block['innerBlocks'] ?? [], $ignored)) as $child) {
|
|
44
|
+
$processed[] = $child;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
$processed[] = $block;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return $processed;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Drupal\silverback_gutenberg\GutenbergValidation;
|
|
4
|
+
|
|
5
|
+
interface GutenbergCardinalityValidatorInterface {
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @var int
|
|
9
|
+
* Value that can be used for the maximum.
|
|
10
|
+
*/
|
|
11
|
+
const CARDINALITY_UNLIMITED = -1;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @var string
|
|
15
|
+
* Specifies if the cardinality check is not limited to a given block type.
|
|
16
|
+
*/
|
|
17
|
+
const CARDINALITY_ANY = 'any';
|
|
18
|
+
|
|
19
|
+
}
|