@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,439 @@
|
|
|
1
|
+
# Silverback Gutenberg
|
|
2
|
+
|
|
3
|
+
Helps integrating Drupal's
|
|
4
|
+
[Gutenberg module](https://www.drupal.org/project/gutenberg) into Silverback
|
|
5
|
+
projects.
|
|
6
|
+
|
|
7
|
+
## GraphQL directives
|
|
8
|
+
|
|
9
|
+
This module provides a set of GraphQL directives that are picked up by the
|
|
10
|
+
`amazeelabs/graphql_directives` module. This allows to easily expose Gutenberg
|
|
11
|
+
blocks through a GraphQL schema.
|
|
12
|
+
|
|
13
|
+
### `@resolveEdtiorBlocks`
|
|
14
|
+
|
|
15
|
+
Parse the raw output of a field at a given path and expose its content as
|
|
16
|
+
structured block data. Allows to define `aggregated` and `ignored` blocks:
|
|
17
|
+
|
|
18
|
+
- `aggregated`: All subsequent blocks of these types will be merged into one
|
|
19
|
+
`core/paragraph` block. In Gutenberg, standard HTML elements like lists,
|
|
20
|
+
headings or tables are represented as separate blocks. This directive allows
|
|
21
|
+
to merge them into one and simplify handling in the frontend.
|
|
22
|
+
- `ignored`: Blocks of these types will be ignored. This is useful for blocks
|
|
23
|
+
that are not relevant for the frontend, like the `core/group` block. The block
|
|
24
|
+
will simply not part of the result and any children are spread where the block
|
|
25
|
+
was.
|
|
26
|
+
|
|
27
|
+
```graphql
|
|
28
|
+
type Page {
|
|
29
|
+
title: String! @resolveProperty(path: "title.value")
|
|
30
|
+
content: [Blocks!]!
|
|
31
|
+
@resolveEditorBlocks(
|
|
32
|
+
path: "body.value"
|
|
33
|
+
aggregated: ["core/paragraph", "core/list"]
|
|
34
|
+
ignored: ["core/group"]
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### `@resolveEditorBlockType`
|
|
40
|
+
|
|
41
|
+
Retrieve the type of gutenberg block. Useful for resolving types of a block
|
|
42
|
+
union.
|
|
43
|
+
|
|
44
|
+
```graphql
|
|
45
|
+
union Blocks @resolveEditorBlockType = Paragraph | Heading | List
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### `@resolveEditorBlockMarkup`
|
|
49
|
+
|
|
50
|
+
Extract inner markup of a block that was provided by the user via rich HTML.
|
|
51
|
+
|
|
52
|
+
```graphql
|
|
53
|
+
type Text @type(id: "core/paragraph") {
|
|
54
|
+
content: String @resolveEditorBlockMarkup
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### `@resolveEditorBlockAttribute`
|
|
59
|
+
|
|
60
|
+
Retrieve a specific attribute, stored in a block.
|
|
61
|
+
|
|
62
|
+
```graphql
|
|
63
|
+
type Figure @type(id: "custom/figure") {
|
|
64
|
+
caption: String @resolveEditorBlockAttribute(key: "caption")
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### `@resolveEditorBlockMedia`
|
|
69
|
+
|
|
70
|
+
Resolve a media entity referenced in a block.
|
|
71
|
+
|
|
72
|
+
```graphql
|
|
73
|
+
type Figure @type(id: "custom/figure") {
|
|
74
|
+
image: Image @resolveEditorBlockMedia
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### `@resolveEditorBlockChildren`
|
|
79
|
+
|
|
80
|
+
Extract all child blocks of a given block.
|
|
81
|
+
|
|
82
|
+
```graphql
|
|
83
|
+
type Columns @type(id: "custom/columns") {
|
|
84
|
+
columns: [ColumnBlocks!]! @resolveEditorBlockChildren
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## LinkProcessor
|
|
89
|
+
|
|
90
|
+
The main idea is that all links added to a Gutenberg page are
|
|
91
|
+
|
|
92
|
+
- kept in internal format (e.g. `/node/123`) when saved to Drupal database
|
|
93
|
+
- processed to language-prefixed aliased form (e.g. `/en/my-page`) when
|
|
94
|
+
- they are displayed in Gutenberg editor
|
|
95
|
+
- they are sent out via GraphQL
|
|
96
|
+
|
|
97
|
+
This helps to
|
|
98
|
+
|
|
99
|
+
- always display fresh path aliases
|
|
100
|
+
- be sure that the language prefix is correct
|
|
101
|
+
- update link URLs when translating content (e.g. `/en/my-page` will become
|
|
102
|
+
`/fr/ma-page` automatically because it's `/node/123` under the hood)
|
|
103
|
+
- keep track of entity usage (TBD)
|
|
104
|
+
|
|
105
|
+
### Implementation
|
|
106
|
+
|
|
107
|
+
The module does most of the things automatically. Yet there are few things
|
|
108
|
+
developers should take care of.
|
|
109
|
+
|
|
110
|
+
First, custom Gutenberg blocks which store links in block attributes should
|
|
111
|
+
implement `hook_silverback_gutenberg_link_processor_block_attrs_alter`. See
|
|
112
|
+
[`silverback_gutenberg.api.php`](./silverback_gutenberg.api.php) for an example.
|
|
113
|
+
|
|
114
|
+
Next, GraphQL resolvers which parse Gutenberg code should call
|
|
115
|
+
`LinkProcessor::processLinks` before parsing the blocks. See
|
|
116
|
+
[`DataProducer/Gutenberg.php`](../../../../apps/silverback-drupal/web/modules/custom/silverback_gatsby_test/src/Plugin/GraphQL/DataProducer/Gutenberg.php)
|
|
117
|
+
for an example.
|
|
118
|
+
|
|
119
|
+
## Validation
|
|
120
|
+
|
|
121
|
+
Custom validator plugins can be created in
|
|
122
|
+
`src/Plugin/Validation/GutenbergValidator`
|
|
123
|
+
|
|
124
|
+
### Field level validation
|
|
125
|
+
|
|
126
|
+
Example: to validate that an email is valid and required.
|
|
127
|
+
|
|
128
|
+
- the block name is `custom/my-block`
|
|
129
|
+
- the field attribute is `email` and the label `Email`
|
|
130
|
+
|
|
131
|
+
```php
|
|
132
|
+
<?php
|
|
133
|
+
|
|
134
|
+
namespace Drupal\custom_gutenberg\Plugin\Validation\GutenbergValidator;
|
|
135
|
+
|
|
136
|
+
use Drupal\silverback_gutenberg\GutenbergValidation\GutenbergValidatorBase;
|
|
137
|
+
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* @GutenbergValidator(
|
|
141
|
+
* id="my_block_validator",
|
|
142
|
+
* label = @Translation("My block validator")
|
|
143
|
+
* )
|
|
144
|
+
*/
|
|
145
|
+
class MyBlockValidator extends GutenbergValidatorBase {
|
|
146
|
+
|
|
147
|
+
use StringTranslationTrait;
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* {@inheritDoc}
|
|
151
|
+
*/
|
|
152
|
+
public function applies(array $block) {
|
|
153
|
+
return $block['blockName'] === 'custom/my-block';
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* {@inheritDoc}
|
|
158
|
+
*/
|
|
159
|
+
public function validatedFields(array $block = []) {
|
|
160
|
+
return [
|
|
161
|
+
'email' => [
|
|
162
|
+
'field_label' => $this->t('Email'),
|
|
163
|
+
'rules' => ['required', 'email'],
|
|
164
|
+
],
|
|
165
|
+
];
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Block level validation
|
|
172
|
+
|
|
173
|
+
Perform custom block validation logic then return the result.
|
|
174
|
+
|
|
175
|
+
```php
|
|
176
|
+
public function validateContent(array $block) {
|
|
177
|
+
$isValid = TRUE;
|
|
178
|
+
|
|
179
|
+
// Custom validation logic.
|
|
180
|
+
// (...)
|
|
181
|
+
|
|
182
|
+
if (!$isValid) {
|
|
183
|
+
return [
|
|
184
|
+
'is_valid' => FALSE,
|
|
185
|
+
'message' => 'Message',
|
|
186
|
+
];
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Passes validation.
|
|
190
|
+
return [
|
|
191
|
+
'is_valid' => TRUE,
|
|
192
|
+
'message' => '',
|
|
193
|
+
];
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Cardinality validation
|
|
198
|
+
|
|
199
|
+
#### Backend
|
|
200
|
+
|
|
201
|
+
Uses the `validateContent()` method as a wrapper, with the cardinality validator
|
|
202
|
+
trait.
|
|
203
|
+
|
|
204
|
+
```php
|
|
205
|
+
use GutenbergCardinalityValidatorTrait;
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Validate a given block type for inner blocks.
|
|
209
|
+
|
|
210
|
+
```php
|
|
211
|
+
public function validateContent(array $block) {
|
|
212
|
+
$expectedChildren = [
|
|
213
|
+
[
|
|
214
|
+
'blockName' => 'custom/teaser',
|
|
215
|
+
'blockLabel' => $this->t('Teaser'),
|
|
216
|
+
'min' => 1,
|
|
217
|
+
'max' => 2,
|
|
218
|
+
],
|
|
219
|
+
];
|
|
220
|
+
return $this->validateCardinality($block, $expectedChildren);
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Validate any kind of block type for inner blocks.
|
|
225
|
+
|
|
226
|
+
```php
|
|
227
|
+
public function validateContent(array $block) {
|
|
228
|
+
$expectedChildren = [
|
|
229
|
+
'validationType' => GutenbergCardinalityValidatorInterface::CARDINALITY_ANY,
|
|
230
|
+
'min' => 0,
|
|
231
|
+
'max' => 1,
|
|
232
|
+
];
|
|
233
|
+
return $this->validateCardinality($block, $expectedChildren);
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
Validate a minimum with no maximum.
|
|
238
|
+
|
|
239
|
+
```php
|
|
240
|
+
public function validateContent(array $block) {
|
|
241
|
+
$expectedChildren = [
|
|
242
|
+
[
|
|
243
|
+
'blockName' => 'custom/teaser',
|
|
244
|
+
'blockLabel' => $this->t('Teaser'),
|
|
245
|
+
'min' => 1,
|
|
246
|
+
'max' => GutenbergCardinalityValidatorInterface::CARDINALITY_UNLIMITED,
|
|
247
|
+
],
|
|
248
|
+
];
|
|
249
|
+
return $this->validateCardinality($block, $expectedChildren);
|
|
250
|
+
}
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
#### Client side alternative
|
|
254
|
+
|
|
255
|
+
Client side cardinality validation can also be done in custom blocks with this
|
|
256
|
+
pattern.
|
|
257
|
+
|
|
258
|
+
- use `getBlockCount`
|
|
259
|
+
- remove the `InnerBlocks` appender when the limit is reached
|
|
260
|
+
|
|
261
|
+
```tsx
|
|
262
|
+
/* global Drupal */
|
|
263
|
+
import { registerBlockType } from 'wordpress__blocks';
|
|
264
|
+
import { InnerBlocks } from 'wordpress__block-editor';
|
|
265
|
+
import { useSelect } from 'wordpress__data';
|
|
266
|
+
|
|
267
|
+
// @ts-ignore
|
|
268
|
+
const __ = Drupal.t;
|
|
269
|
+
|
|
270
|
+
const MAX_BLOCKS: number = 1;
|
|
271
|
+
|
|
272
|
+
registerBlockType('custom/my-block', {
|
|
273
|
+
title: __('My Block'),
|
|
274
|
+
icon: 'location',
|
|
275
|
+
category: 'layout',
|
|
276
|
+
attributes: {},
|
|
277
|
+
edit: (props) => {
|
|
278
|
+
const { blockCount } = useSelect((select) => ({
|
|
279
|
+
blockCount: select('core/block-editor').getBlockCount(props.clientId),
|
|
280
|
+
}));
|
|
281
|
+
return (
|
|
282
|
+
<div>
|
|
283
|
+
<InnerBlocks
|
|
284
|
+
templateLock={false}
|
|
285
|
+
renderAppender={() => {
|
|
286
|
+
if (blockCount >= MAX_BLOCKS) {
|
|
287
|
+
return null;
|
|
288
|
+
} else {
|
|
289
|
+
return <InnerBlocks.ButtonBlockAppender />;
|
|
290
|
+
}
|
|
291
|
+
}}
|
|
292
|
+
allowedBlocks={['core/block']}
|
|
293
|
+
template={[]}
|
|
294
|
+
/>
|
|
295
|
+
</div>
|
|
296
|
+
);
|
|
297
|
+
},
|
|
298
|
+
save: () => {
|
|
299
|
+
return <InnerBlocks.Content />;
|
|
300
|
+
},
|
|
301
|
+
});
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
## Linkit integration
|
|
305
|
+
|
|
306
|
+
To enable the integration:
|
|
307
|
+
|
|
308
|
+
- Enable the linkit module and create a linkit profile with `gutenberg` machine
|
|
309
|
+
name
|
|
310
|
+
<details>
|
|
311
|
+
<summary>This brings</summary>
|
|
312
|
+
|
|
313
|
+
- Basic linkit integration
|
|
314
|
+
- Improved suggestion labels (e.g. `Content: Page`, `Media: PDF` instead of
|
|
315
|
+
`page`, `pdf`)
|
|
316
|
+
|
|
317
|
+
</details>
|
|
318
|
+
|
|
319
|
+
- Add `Silverback:` prefixed matchers to the profile
|
|
320
|
+
<details> <summary>How they differ from the default linkit matchers</summary>
|
|
321
|
+
|
|
322
|
+
- Suggestions order is done by the position of the search string in the label.
|
|
323
|
+
For example, if you search for "best", the order will be:
|
|
324
|
+
- _Best_ in class
|
|
325
|
+
- The _best_ choice
|
|
326
|
+
- Always choose _best_
|
|
327
|
+
- Improved display of translated content. By default, linkit searches through
|
|
328
|
+
all content translations but displays suggestions in the current language.
|
|
329
|
+
Which can be confusing. The Silverback matchers changes this a bit. If the
|
|
330
|
+
displayed item does not contain the prompt, a translation containing the
|
|
331
|
+
prompt will be added in the brackets. For example, if you search for "gift"
|
|
332
|
+
with the English UI, the suggestions will look like this:
|
|
333
|
+
|
|
334
|
+
- _Gift_ for a friend
|
|
335
|
+
- Poison for an enemy (_Gift_ für einen Feind)
|
|
336
|
+
</details>
|
|
337
|
+
|
|
338
|
+
- To use a different profile when using the LinkControl component, add the
|
|
339
|
+
machine name of the profile to the `subtype` query parameter in the
|
|
340
|
+
component prop `suggestionsQuery` like below, where the custom linkit
|
|
341
|
+
profile is called `customer`.
|
|
342
|
+
|
|
343
|
+
```
|
|
344
|
+
<DrupalLinkControl
|
|
345
|
+
searchInputPlaceholder={__('Target page')}
|
|
346
|
+
value={{
|
|
347
|
+
url: props.attributes.linkUrl,
|
|
348
|
+
}}
|
|
349
|
+
settings={[]}
|
|
350
|
+
suggestionsQuery={{
|
|
351
|
+
// Use the custom linkit profile called customer.
|
|
352
|
+
subtype: 'customer',
|
|
353
|
+
}}
|
|
354
|
+
onChange={(link) => {
|
|
355
|
+
props.setAttributes({
|
|
356
|
+
linkUrl: link.url,
|
|
357
|
+
});
|
|
358
|
+
}}
|
|
359
|
+
/>
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
## Gutenberg block mutator
|
|
363
|
+
|
|
364
|
+
Entity id references can be used as Gutenberg attributes.
|
|
365
|
+
|
|
366
|
+
When using default content, we need to map the entity id with the uuid:
|
|
367
|
+
|
|
368
|
+
- id to uuid on export
|
|
369
|
+
- uuid to id on import
|
|
370
|
+
|
|
371
|
+
This is especially useful for schema tests, when using entity reference.
|
|
372
|
+
|
|
373
|
+
To facilitate this process, block mutator plugins can be used, the easiest way
|
|
374
|
+
is to extend the `EntityBlockMutatorBase` base class.
|
|
375
|
+
|
|
376
|
+
Example, with multi-valued Gutenberg attribute
|
|
377
|
+
|
|
378
|
+
```php
|
|
379
|
+
<?php
|
|
380
|
+
|
|
381
|
+
namespace Drupal\silverback_gutenberg\Plugin\GutenbergBlockMutator;
|
|
382
|
+
|
|
383
|
+
use Drupal\silverback_gutenberg\Attribute\GutenbergBlockMutator;
|
|
384
|
+
use Drupal\silverback_gutenberg\BlockMutator\EntityBlockMutatorBase;
|
|
385
|
+
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
|
386
|
+
|
|
387
|
+
#[GutenbergBlockMutator(
|
|
388
|
+
id: "media_block_mutator",
|
|
389
|
+
label: new TranslatableMarkup("Media IDs to UUIDs and viceversa."),
|
|
390
|
+
)]
|
|
391
|
+
class MediaBlockMutator extends EntityBlockMutatorBase {
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* {@inheritDoc}
|
|
395
|
+
*/
|
|
396
|
+
public bool $isMultiple = TRUE;
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* {@inheritDoc}
|
|
400
|
+
*/
|
|
401
|
+
public string $gutenbergAttribute = 'mediaEntityIds';
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* {@inheritDoc}
|
|
405
|
+
*/
|
|
406
|
+
public string $entityTypeId = 'media';
|
|
407
|
+
|
|
408
|
+
}
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
Example, with single-valued Gutenberg attribute
|
|
412
|
+
|
|
413
|
+
```php
|
|
414
|
+
<?php
|
|
415
|
+
|
|
416
|
+
namespace Drupal\silverback_gutenberg\Plugin\GutenbergBlockMutator;
|
|
417
|
+
|
|
418
|
+
use Drupal\silverback_gutenberg\Attribute\GutenbergBlockMutator;
|
|
419
|
+
use Drupal\silverback_gutenberg\BlockMutator\EntityBlockMutatorBase;
|
|
420
|
+
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
|
421
|
+
|
|
422
|
+
#[GutenbergBlockMutator(
|
|
423
|
+
id: "node_block_mutator",
|
|
424
|
+
label: new TranslatableMarkup("Node ID to UUID and viceversa."),
|
|
425
|
+
)]
|
|
426
|
+
class NodeBlockMutator extends EntityBlockMutatorBase {
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* {@inheritDoc}
|
|
430
|
+
*/
|
|
431
|
+
public string $gutenbergAttribute = 'nodeId';
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* {@inheritDoc}
|
|
435
|
+
*/
|
|
436
|
+
public string $entityTypeId = 'node';
|
|
437
|
+
|
|
438
|
+
}
|
|
439
|
+
```
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "amazeelabs/silverback_gutenberg",
|
|
3
|
+
"type": "drupal-module",
|
|
4
|
+
"version": "2.6.1",
|
|
5
|
+
"description": "Adjusts Gutenberg for Amazee Labs needs.",
|
|
6
|
+
"homepage": "https://github.com/AmazeeLabs/silverback-mono/tree/development/packages/composer/amazeelabs/silverback_gutenberg#readme",
|
|
7
|
+
"license": "GPL-2.0+",
|
|
8
|
+
"repositories": [
|
|
9
|
+
{
|
|
10
|
+
"type": "composer",
|
|
11
|
+
"url": "https://packages.drupal.org/8"
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"require": {
|
|
15
|
+
"drupal/gutenberg": "^2.14.0"
|
|
16
|
+
},
|
|
17
|
+
"conflict": {
|
|
18
|
+
"drupal/linkit": "<7.0.0"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
local_hosts: []
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
silverback_gutenberg.settings:
|
|
2
|
+
type: config_object
|
|
3
|
+
label: 'Silverback Gutenberg settings'
|
|
4
|
+
mapping:
|
|
5
|
+
local_hosts:
|
|
6
|
+
type: sequence
|
|
7
|
+
nullable: true
|
|
8
|
+
label:
|
|
9
|
+
'URLs with these hosts will be turned to relative. If null, any absolute
|
|
10
|
+
URLs will NOT be processed (BC behavior).'
|
|
11
|
+
sequence:
|
|
12
|
+
type: string
|
|
13
|
+
label: 'Host'
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/* There is no other way to disable the columns amount slider. */
|
|
2
|
+
.components-range-control {
|
|
3
|
+
display: none;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
/* Hide the Upload button as it skips the media edit form (which may cause
|
|
7
|
+
required fields to be left blank). Users still can upload new files via the
|
|
8
|
+
media dialog. */
|
|
9
|
+
.block-editor-media-placeholder .components-form-file-upload {
|
|
10
|
+
display: none;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/* Hide the "Welcome to Gutenberg" overlay, since it is annoying and messes
|
|
14
|
+
with cypress tests.*/
|
|
15
|
+
.components-modal__screen-overlay {
|
|
16
|
+
display: none !important;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/* Make the link suggestions box wider (for real world page titles) */
|
|
20
|
+
.components-popover__content div.block-editor-link-control {
|
|
21
|
+
width: 600px;
|
|
22
|
+
max-width: 600px;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/* Do not trim the link suggestions. */
|
|
26
|
+
.block-editor-link-control__search-item
|
|
27
|
+
span.block-editor-link-control__search-item-title {
|
|
28
|
+
white-space: normal;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/* Avoid word wrap in the type label. */
|
|
32
|
+
.block-editor-link-control__search-item
|
|
33
|
+
.block-editor-link-control__search-item-type {
|
|
34
|
+
white-space: pre;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/* Validator */
|
|
38
|
+
.block-editor-block-list__layout .not-valid {
|
|
39
|
+
filter: drop-shadow(2px 2px 5px #cc1818);
|
|
40
|
+
background: white;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/* Hide the preview button. */
|
|
44
|
+
.block-editor-post-preview__dropdown {
|
|
45
|
+
display: none;
|
|
46
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""
|
|
2
|
+
implementation(drupal): \Drupal\silverback_gutenberg\Directives::editorBlockChildren
|
|
3
|
+
"""
|
|
4
|
+
directive @resolveEditorBlockChildren repeatable on FIELD_DEFINITION
|
|
5
|
+
|
|
6
|
+
"""
|
|
7
|
+
implementation(drupal): \Drupal\silverback_gutenberg\Directives::editorBlockMarkup
|
|
8
|
+
"""
|
|
9
|
+
directive @resolveEditorBlockMarkup repeatable on FIELD_DEFINITION
|
|
10
|
+
|
|
11
|
+
"""
|
|
12
|
+
implementation(drupal): \Drupal\silverback_gutenberg\Directives::editorBlockMedia
|
|
13
|
+
"""
|
|
14
|
+
directive @resolveEditorBlockMedia repeatable on FIELD_DEFINITION
|
|
15
|
+
|
|
16
|
+
"""
|
|
17
|
+
implementation(drupal): \Drupal\silverback_gutenberg\Directives::editorBlockType
|
|
18
|
+
"""
|
|
19
|
+
directive @resolveEditorBlockType repeatable on FIELD_DEFINITION | UNION | ENUM | INTERFACE
|
|
20
|
+
|
|
21
|
+
"""
|
|
22
|
+
Parse a gutenberg document into block data.
|
|
23
|
+
|
|
24
|
+
implementation(drupal): \Drupal\silverback_gutenberg\Directives::editorBlocks
|
|
25
|
+
"""
|
|
26
|
+
directive @resolveEditorBlocks(
|
|
27
|
+
path: String!
|
|
28
|
+
ignored: [String!]
|
|
29
|
+
aggregated: [String!]
|
|
30
|
+
) repeatable on FIELD_DEFINITION
|
|
31
|
+
|
|
32
|
+
"""
|
|
33
|
+
Retrieve an editor block attribute.
|
|
34
|
+
|
|
35
|
+
implementation(drupal): \Drupal\silverback_gutenberg\Directives::editorBlockAttribute
|
|
36
|
+
"""
|
|
37
|
+
directive @resolveEditorBlockAttribute(
|
|
38
|
+
key: String!
|
|
39
|
+
plainText: Boolean
|
|
40
|
+
) repeatable on FIELD_DEFINITION | UNION | INTERFACE
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Provided by the "silverback_gutenberg" module.
|
|
3
|
+
Implemented in "Drupal\silverback_gutenberg\Plugin\GraphQL\Directive\EditorBlockChildren".
|
|
4
|
+
"""
|
|
5
|
+
directive @resolveEditorBlockChildren repeatable on FIELD_DEFINITION | SCALAR | UNION | ENUM | INTERFACE | OBJECT
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
Provided by the "silverback_gutenberg" module.
|
|
9
|
+
Implemented in "Drupal\silverback_gutenberg\Plugin\GraphQL\Directive\EditorBlockMarkup".
|
|
10
|
+
"""
|
|
11
|
+
directive @resolveEditorBlockMarkup repeatable on FIELD_DEFINITION | SCALAR | UNION | ENUM | INTERFACE | OBJECT
|
|
12
|
+
|
|
13
|
+
"""
|
|
14
|
+
Provided by the "silverback_gutenberg" module.
|
|
15
|
+
Implemented in "Drupal\silverback_gutenberg\Plugin\GraphQL\Directive\EditorBlockMedia".
|
|
16
|
+
"""
|
|
17
|
+
directive @resolveEditorBlockMedia repeatable on FIELD_DEFINITION | SCALAR | UNION | ENUM | INTERFACE | OBJECT
|
|
18
|
+
|
|
19
|
+
"""
|
|
20
|
+
Provided by the "silverback_gutenberg" module.
|
|
21
|
+
Implemented in "Drupal\silverback_gutenberg\Plugin\GraphQL\Directive\EditorBlockType".
|
|
22
|
+
"""
|
|
23
|
+
directive @resolveEditorBlockType repeatable on FIELD_DEFINITION | SCALAR | UNION | ENUM | INTERFACE | OBJECT
|
|
24
|
+
|
|
25
|
+
"""
|
|
26
|
+
Parse a gutenberg document into block data.
|
|
27
|
+
|
|
28
|
+
Provided by the "silverback_gutenberg" module.
|
|
29
|
+
Implemented in "Drupal\silverback_gutenberg\Plugin\GraphQL\Directive\EditorBlocks".
|
|
30
|
+
"""
|
|
31
|
+
directive @resolveEditorBlocks(
|
|
32
|
+
path: String!
|
|
33
|
+
ignored: [String!]
|
|
34
|
+
aggregated: [String!]
|
|
35
|
+
) repeatable on FIELD_DEFINITION | SCALAR | UNION | ENUM | INTERFACE | OBJECT
|
|
36
|
+
|
|
37
|
+
"""
|
|
38
|
+
Retrieve an editor block attribute.
|
|
39
|
+
|
|
40
|
+
Provided by the "silverback_gutenberg" module.
|
|
41
|
+
Implemented in "Drupal\silverback_gutenberg\Plugin\GraphQL\Directive\EditorBlockAttribute".
|
|
42
|
+
"""
|
|
43
|
+
directive @resolveEditorBlockAttribute(
|
|
44
|
+
key: String!
|
|
45
|
+
plainText: Boolean
|
|
46
|
+
) repeatable on FIELD_DEFINITION | SCALAR | UNION | ENUM | INTERFACE | OBJECT
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
var silverbackGutenbergUtils = {
|
|
2
|
+
sanitizeText: function (text) {
|
|
3
|
+
return (
|
|
4
|
+
text
|
|
5
|
+
// When copying text from Word, HTML comments are escaped. So we get this:
|
|
6
|
+
// ...<br><!-- /* Font Definitions */ @font-face {...} --><br>...
|
|
7
|
+
// Unescape them back.
|
|
8
|
+
.replace('<!--', '<!--')
|
|
9
|
+
.replace('-->', '-->')
|
|
10
|
+
// Now clean all HTML tags.
|
|
11
|
+
.replace(/(<([^>]+)>)/gi, '')
|
|
12
|
+
);
|
|
13
|
+
},
|
|
14
|
+
|
|
15
|
+
setPlainTextAttribute: function (props, name, text) {
|
|
16
|
+
const sanitizedText = silverbackGutenbergUtils.sanitizeText(text);
|
|
17
|
+
props.setAttributes({
|
|
18
|
+
[name]: sanitizedText,
|
|
19
|
+
});
|
|
20
|
+
if (text !== sanitizedText) {
|
|
21
|
+
props.setState({ rerender: Date.now() });
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
};
|