@brilo/base-blocks 0.1.34 → 0.1.36
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/build/.tsbuildinfo +1 -1
- package/build/blocks/sections/base-section/attributes.d.ts +11 -0
- package/build/blocks/sections/base-section/attributes.d.ts.map +1 -1
- package/build/blocks/sections/base-section/attributes.js +11 -0
- package/build/blocks/sections/base-section/constants.d.ts +84 -0
- package/build/blocks/sections/base-section/constants.d.ts.map +1 -1
- package/build/blocks/sections/base-section/constants.js +45 -0
- package/build/blocks/sections/base-section/controls.d.ts.map +1 -1
- package/build/blocks/sections/base-section/controls.js +40 -4
- package/build/blocks/sections/base-section/edit.d.ts.map +1 -1
- package/build/blocks/sections/base-section/edit.js +21 -5
- package/build/blocks/sections/base-section/types.d.ts +28 -1
- package/build/blocks/sections/base-section/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/blocks/sections/base-section/attributes.ts +11 -0
- package/src/blocks/sections/base-section/constants.ts +55 -0
- package/src/blocks/sections/base-section/controls.tsx +242 -2
- package/src/blocks/sections/base-section/edit.tsx +19 -3
- package/src/blocks/sections/base-section/types.ts +38 -1
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
ButtonGroup,
|
|
6
6
|
PanelBody,
|
|
7
7
|
PanelRow,
|
|
8
|
+
RangeControl,
|
|
8
9
|
SelectControl,
|
|
9
10
|
TextControl,
|
|
10
11
|
__experimentalToggleGroupControl as ToggleGroupControl,
|
|
@@ -13,8 +14,31 @@ import {
|
|
|
13
14
|
} from '@wordpress/components'
|
|
14
15
|
import {__} from '@wordpress/i18n'
|
|
15
16
|
import {useTenantColorPalette} from '~/hooks/useTenantColorPalette'
|
|
16
|
-
import type {
|
|
17
|
+
import type {
|
|
18
|
+
BackgroundImage,
|
|
19
|
+
BackgroundImageFit,
|
|
20
|
+
BackgroundImagePosition,
|
|
21
|
+
BaseBlockAttributes,
|
|
22
|
+
Decoration,
|
|
23
|
+
DecorationDepth,
|
|
24
|
+
DecorationImage,
|
|
25
|
+
DecorationPosition,
|
|
26
|
+
DecorationTranslate,
|
|
27
|
+
ExcludableBaseAttr,
|
|
28
|
+
FluidDecoration,
|
|
29
|
+
FluidDecorationImage,
|
|
30
|
+
} from './types'
|
|
17
31
|
import {
|
|
32
|
+
BACKGROUND_IMAGE_FITS,
|
|
33
|
+
BACKGROUND_IMAGE_OVERLAY_OPACITY_DEFAULT,
|
|
34
|
+
BACKGROUND_IMAGE_POSITIONS,
|
|
35
|
+
DECORATION_DEPTHS,
|
|
36
|
+
DECORATION_MAX_WIDTH_DEFAULT,
|
|
37
|
+
DECORATION_MAX_WIDTH_UNITS,
|
|
38
|
+
DECORATION_OFFSET_DEFAULT,
|
|
39
|
+
DECORATION_OFFSET_UNITS,
|
|
40
|
+
DECORATION_POSITIONS,
|
|
41
|
+
DECORATION_TRANSLATES,
|
|
18
42
|
FLUID_DECORATION_DEFAULT_HEIGHT,
|
|
19
43
|
FLUID_DECORATION_HEIGHT_PRESETS,
|
|
20
44
|
FLUID_DECORATION_UNITS,
|
|
@@ -83,6 +107,210 @@ function FluidDecorationControl({label, value, onChange}: FluidDecorationControl
|
|
|
83
107
|
)
|
|
84
108
|
}
|
|
85
109
|
|
|
110
|
+
interface DecorationControlProps {
|
|
111
|
+
value?: Decoration
|
|
112
|
+
onChange: (value: Decoration | undefined) => void
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function DecorationControl({value, onChange}: DecorationControlProps) {
|
|
116
|
+
const img = value?.img
|
|
117
|
+
const position = value?.position ?? 'top-right'
|
|
118
|
+
const depth = value?.depth ?? 'front'
|
|
119
|
+
const translate = value?.translate ?? 'center'
|
|
120
|
+
const maxWidth = value?.maxWidth ?? DECORATION_MAX_WIDTH_DEFAULT
|
|
121
|
+
const offsetX = value?.offsetX ?? DECORATION_OFFSET_DEFAULT
|
|
122
|
+
const offsetY = value?.offsetY ?? DECORATION_OFFSET_DEFAULT
|
|
123
|
+
|
|
124
|
+
const update = (patch: Partial<Decoration>) => onChange({img, position, depth, translate, maxWidth, offsetX, offsetY, ...value, ...patch})
|
|
125
|
+
|
|
126
|
+
const handleSelect = (media: {id: number; url: string; alt: string; width?: number; height?: number}) => {
|
|
127
|
+
const nextImg: DecorationImage = {id: media.id, src: media.url, alt: media.alt ?? '', width: media.width, height: media.height}
|
|
128
|
+
update({img: nextImg})
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const handleRemove = () => onChange(undefined)
|
|
132
|
+
|
|
133
|
+
return (
|
|
134
|
+
<BaseControl label={__('Obrázek', 'brilo-blocks')} __nextHasNoMarginBottom>
|
|
135
|
+
<MediaUploadCheck>
|
|
136
|
+
<MediaUpload
|
|
137
|
+
onSelect={handleSelect}
|
|
138
|
+
allowedTypes={['image']}
|
|
139
|
+
value={img?.id || 0}
|
|
140
|
+
render={({open}: {open: () => void}) => (
|
|
141
|
+
<PanelRow>
|
|
142
|
+
{img?.src && <img src={img.src} alt={img.alt} style={{maxWidth: 48, maxHeight: 48, objectFit: 'contain'}} />}
|
|
143
|
+
<Button onClick={open} variant="secondary" size="small">
|
|
144
|
+
{img?.src ? __('Změnit', 'brilo-blocks') : __('Vybrat obrázek', 'brilo-blocks')}
|
|
145
|
+
</Button>
|
|
146
|
+
{!!img?.src && (
|
|
147
|
+
<Button onClick={handleRemove} variant="tertiary" size="small" isDestructive>
|
|
148
|
+
{__('Odebrat', 'brilo-blocks')}
|
|
149
|
+
</Button>
|
|
150
|
+
)}
|
|
151
|
+
</PanelRow>
|
|
152
|
+
)}
|
|
153
|
+
/>
|
|
154
|
+
</MediaUploadCheck>
|
|
155
|
+
|
|
156
|
+
{!!img?.src && (
|
|
157
|
+
<>
|
|
158
|
+
<BaseControl label={__('Pozice', 'brilo-blocks')} __nextHasNoMarginBottom>
|
|
159
|
+
<div style={{display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 4}}>
|
|
160
|
+
{DECORATION_POSITIONS.map((p) => (
|
|
161
|
+
<Button
|
|
162
|
+
key={p.value}
|
|
163
|
+
variant="secondary"
|
|
164
|
+
size="small"
|
|
165
|
+
isPressed={position === p.value}
|
|
166
|
+
onClick={() => update({position: p.value as DecorationPosition})}
|
|
167
|
+
>
|
|
168
|
+
{p.label}
|
|
169
|
+
</Button>
|
|
170
|
+
))}
|
|
171
|
+
</div>
|
|
172
|
+
</BaseControl>
|
|
173
|
+
|
|
174
|
+
<ToggleGroupControl
|
|
175
|
+
label={__('Vrstva', 'brilo-blocks')}
|
|
176
|
+
value={depth}
|
|
177
|
+
onChange={(next) => update({depth: next as DecorationDepth})}
|
|
178
|
+
isBlock
|
|
179
|
+
>
|
|
180
|
+
{DECORATION_DEPTHS.map((d) => (
|
|
181
|
+
<ToggleGroupControlOption key={d.value} value={d.value} label={d.label} />
|
|
182
|
+
))}
|
|
183
|
+
</ToggleGroupControl>
|
|
184
|
+
|
|
185
|
+
<ToggleGroupControl
|
|
186
|
+
label={__('Přesah', 'brilo-blocks')}
|
|
187
|
+
value={translate}
|
|
188
|
+
onChange={(next) => update({translate: next as DecorationTranslate})}
|
|
189
|
+
isBlock
|
|
190
|
+
>
|
|
191
|
+
{DECORATION_TRANSLATES.map((t) => (
|
|
192
|
+
<ToggleGroupControlOption key={t.value} value={t.value} label={t.label} />
|
|
193
|
+
))}
|
|
194
|
+
</ToggleGroupControl>
|
|
195
|
+
|
|
196
|
+
<UnitControl
|
|
197
|
+
label={__('Max. šířka obrázku', 'brilo-blocks')}
|
|
198
|
+
value={maxWidth}
|
|
199
|
+
units={DECORATION_MAX_WIDTH_UNITS}
|
|
200
|
+
onChange={(next) => update({maxWidth: (next as string) ?? DECORATION_MAX_WIDTH_DEFAULT})}
|
|
201
|
+
/>
|
|
202
|
+
|
|
203
|
+
<BaseControl
|
|
204
|
+
label={__('Doladění pozice', 'brilo-blocks')}
|
|
205
|
+
__nextHasNoMarginBottom
|
|
206
|
+
help={__('Ruční posun obrázku navíc k vybrané pozici.', 'brilo-blocks')}
|
|
207
|
+
>
|
|
208
|
+
<UnitControl
|
|
209
|
+
label={__('Posun X', 'brilo-blocks')}
|
|
210
|
+
value={offsetX}
|
|
211
|
+
units={DECORATION_OFFSET_UNITS}
|
|
212
|
+
onChange={(next) => update({offsetX: (next as string) ?? DECORATION_OFFSET_DEFAULT})}
|
|
213
|
+
/>
|
|
214
|
+
<UnitControl
|
|
215
|
+
label={__('Posun Y', 'brilo-blocks')}
|
|
216
|
+
value={offsetY}
|
|
217
|
+
units={DECORATION_OFFSET_UNITS}
|
|
218
|
+
onChange={(next) => update({offsetY: (next as string) ?? DECORATION_OFFSET_DEFAULT})}
|
|
219
|
+
/>
|
|
220
|
+
</BaseControl>
|
|
221
|
+
</>
|
|
222
|
+
)}
|
|
223
|
+
</BaseControl>
|
|
224
|
+
)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
interface BackgroundImageControlProps {
|
|
228
|
+
value?: BackgroundImage
|
|
229
|
+
onChange: (value: BackgroundImage | undefined) => void
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function BackgroundImageControl({value, onChange}: BackgroundImageControlProps) {
|
|
233
|
+
const {paletteColors} = useTenantColorPalette()
|
|
234
|
+
const img = value?.img
|
|
235
|
+
const fit = value?.fit ?? 'cover'
|
|
236
|
+
const position = value?.position ?? 'center center'
|
|
237
|
+
const overlayColor = value?.overlay?.color
|
|
238
|
+
const overlayOpacity = value?.overlay?.opacity ?? BACKGROUND_IMAGE_OVERLAY_OPACITY_DEFAULT
|
|
239
|
+
|
|
240
|
+
const update = (patch: Partial<BackgroundImage>) => onChange({img, fit, position, overlay: value?.overlay, ...value, ...patch})
|
|
241
|
+
|
|
242
|
+
const handleSelect = (media: {id: number; url: string; alt: string; width?: number; height?: number}) => {
|
|
243
|
+
const nextImg: FluidDecorationImage = {id: media.id, src: media.url, alt: media.alt ?? '', width: media.width, height: media.height}
|
|
244
|
+
update({img: nextImg})
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const handleRemove = () => onChange(undefined)
|
|
248
|
+
|
|
249
|
+
return (
|
|
250
|
+
<BaseControl label={__('Obrázek pozadí', 'brilo-blocks')} __nextHasNoMarginBottom>
|
|
251
|
+
<MediaUploadCheck>
|
|
252
|
+
<MediaUpload
|
|
253
|
+
onSelect={handleSelect}
|
|
254
|
+
allowedTypes={['image']}
|
|
255
|
+
value={img?.id || 0}
|
|
256
|
+
render={({open}: {open: () => void}) => (
|
|
257
|
+
<PanelRow>
|
|
258
|
+
{img?.src && <img src={img.src} alt={img.alt} style={{maxWidth: 48, maxHeight: 48, objectFit: 'contain'}} />}
|
|
259
|
+
<Button onClick={open} variant="secondary" size="small">
|
|
260
|
+
{img?.src ? __('Změnit', 'brilo-blocks') : __('Vybrat obrázek', 'brilo-blocks')}
|
|
261
|
+
</Button>
|
|
262
|
+
{!!img?.src && (
|
|
263
|
+
<Button onClick={handleRemove} variant="tertiary" size="small" isDestructive>
|
|
264
|
+
{__('Odebrat', 'brilo-blocks')}
|
|
265
|
+
</Button>
|
|
266
|
+
)}
|
|
267
|
+
</PanelRow>
|
|
268
|
+
)}
|
|
269
|
+
/>
|
|
270
|
+
</MediaUploadCheck>
|
|
271
|
+
|
|
272
|
+
{!!img?.src && (
|
|
273
|
+
<>
|
|
274
|
+
<ToggleGroupControl
|
|
275
|
+
label={__('Přizpůsobení', 'brilo-blocks')}
|
|
276
|
+
value={fit}
|
|
277
|
+
onChange={(next) => update({fit: next as BackgroundImageFit})}
|
|
278
|
+
isBlock
|
|
279
|
+
>
|
|
280
|
+
{BACKGROUND_IMAGE_FITS.map((f) => (
|
|
281
|
+
<ToggleGroupControlOption key={f.value} value={f.value} label={f.label} />
|
|
282
|
+
))}
|
|
283
|
+
</ToggleGroupControl>
|
|
284
|
+
|
|
285
|
+
<SelectControl<string>
|
|
286
|
+
label={__('Pozice', 'brilo-blocks')}
|
|
287
|
+
value={position}
|
|
288
|
+
options={[...BACKGROUND_IMAGE_POSITIONS]}
|
|
289
|
+
onChange={(next) => update({position: next as BackgroundImagePosition})}
|
|
290
|
+
/>
|
|
291
|
+
|
|
292
|
+
<BaseControl label={__('Překryv barvou', 'brilo-blocks')} __nextHasNoMarginBottom help={__('Ztmavení/zabarvení obrázku pro lepší čitelnost textu.', 'brilo-blocks')}>
|
|
293
|
+
<ColorPalette
|
|
294
|
+
colors={paletteColors}
|
|
295
|
+
value={overlayColor || undefined}
|
|
296
|
+
onChange={(next) => update({overlay: {color: next ?? undefined, opacity: overlayOpacity}})}
|
|
297
|
+
/>
|
|
298
|
+
{!!overlayColor && (
|
|
299
|
+
<RangeControl
|
|
300
|
+
label={__('Krytí překryvu', 'brilo-blocks')}
|
|
301
|
+
value={overlayOpacity}
|
|
302
|
+
min={0}
|
|
303
|
+
max={100}
|
|
304
|
+
onChange={(next) => update({overlay: {color: overlayColor, opacity: next ?? BACKGROUND_IMAGE_OVERLAY_OPACITY_DEFAULT}})}
|
|
305
|
+
/>
|
|
306
|
+
)}
|
|
307
|
+
</BaseControl>
|
|
308
|
+
</>
|
|
309
|
+
)}
|
|
310
|
+
</BaseControl>
|
|
311
|
+
)
|
|
312
|
+
}
|
|
313
|
+
|
|
86
314
|
type SpacingPosition = BaseBlockAttributes['spacing']['position']
|
|
87
315
|
type SpacingSize = BaseBlockAttributes['spacing']['size']
|
|
88
316
|
|
|
@@ -93,7 +321,7 @@ interface BaseControlsProps {
|
|
|
93
321
|
}
|
|
94
322
|
|
|
95
323
|
export function BaseControls({attributes, setAttributes, exclude}: BaseControlsProps) {
|
|
96
|
-
const {id, spacing, background, textColor, decorations} = attributes
|
|
324
|
+
const {id, spacing, background, textColor, decoration, decorations, backgroundImage} = attributes
|
|
97
325
|
const {paletteColors, resolveHex} = useTenantColorPalette()
|
|
98
326
|
const fluid = decorations?.fluid ?? {}
|
|
99
327
|
|
|
@@ -143,6 +371,18 @@ export function BaseControls({attributes, setAttributes, exclude}: BaseControlsP
|
|
|
143
371
|
</PanelBody>
|
|
144
372
|
)}
|
|
145
373
|
|
|
374
|
+
{!exclude.includes('backgroundImage') && (
|
|
375
|
+
<PanelBody title={__('Obrázek pozadí', 'brilo-blocks')} initialOpen={false}>
|
|
376
|
+
<BackgroundImageControl value={backgroundImage} onChange={(value) => setAttributes({backgroundImage: value ?? {}})} />
|
|
377
|
+
</PanelBody>
|
|
378
|
+
)}
|
|
379
|
+
|
|
380
|
+
{!exclude.includes('decoration') && (
|
|
381
|
+
<PanelBody title={__('Dekorace', 'brilo-blocks')} initialOpen={false}>
|
|
382
|
+
<DecorationControl value={decoration} onChange={(value) => setAttributes({decoration: value ?? {}})} />
|
|
383
|
+
</PanelBody>
|
|
384
|
+
)}
|
|
385
|
+
|
|
146
386
|
{!exclude.includes('decorations') && (
|
|
147
387
|
<PanelBody title={__('Fluidní dekorace', 'brilo-blocks')} initialOpen={false}>
|
|
148
388
|
<FluidDecorationControl label={__('Nahoře', 'brilo-blocks')} value={fluid.top} onChange={(value) => setFluid('top', value)} />
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {InspectorControls, useBlockProps, useInnerBlocksProps} from '@wordpress/block-editor'
|
|
2
2
|
import type {BlockEditProps} from '@wordpress/blocks'
|
|
3
|
+
import type {CSSProperties} from 'react'
|
|
3
4
|
import type {BaseBlockAttributes, BaseSectionOptions} from './types'
|
|
4
5
|
import {BaseControls} from './controls'
|
|
5
6
|
|
|
@@ -8,13 +9,28 @@ export function createBaseSectionEdit<TExtra extends Record<string, unknown> = R
|
|
|
8
9
|
|
|
9
10
|
return function Edit(props: BlockEditProps<BaseBlockAttributes & TExtra>) {
|
|
10
11
|
const {attributes, setAttributes} = props
|
|
11
|
-
const {background, textColor, gradient} = attributes as BaseBlockAttributes & {gradient?: string}
|
|
12
|
+
const {background, textColor, gradient, backgroundImage} = attributes as BaseBlockAttributes & {gradient?: string}
|
|
13
|
+
const bgImageSrc = backgroundImage?.img?.src
|
|
14
|
+
// A plain background-color/gradient always paints behind an image, so it can never tint it. To preview a
|
|
15
|
+
// translucent color/gradient sitting on top of the image (matching frontend), stack it as its own layer
|
|
16
|
+
// in front of the `url(...)` layer instead of setting backgroundColor separately.
|
|
17
|
+
const tintLayer = background ? `linear-gradient(${background}, ${background})` : gradient
|
|
12
18
|
const blockProps = useBlockProps({
|
|
13
19
|
className: options.blockClassName,
|
|
14
20
|
style: {
|
|
15
|
-
|
|
16
|
-
...(
|
|
21
|
+
position: 'relative' as CSSProperties['position'],
|
|
22
|
+
...(background && !bgImageSrc ? {backgroundColor: background, padding: '16px'} : {}),
|
|
23
|
+
...(gradient && !bgImageSrc ? {background: gradient, padding: '16px'} : {}),
|
|
17
24
|
...(textColor ? {color: textColor} : {}),
|
|
25
|
+
...(bgImageSrc
|
|
26
|
+
? {
|
|
27
|
+
backgroundImage: [tintLayer, `url(${bgImageSrc})`].filter(Boolean).join(', '),
|
|
28
|
+
backgroundSize: [tintLayer ? '100% 100%' : null, backgroundImage?.fit ?? 'cover'].filter(Boolean).join(', '),
|
|
29
|
+
backgroundPosition: [tintLayer ? 'center center' : null, backgroundImage?.position ?? 'center center'].filter(Boolean).join(', '),
|
|
30
|
+
backgroundRepeat: 'no-repeat',
|
|
31
|
+
padding: '16px',
|
|
32
|
+
}
|
|
33
|
+
: {}),
|
|
18
34
|
},
|
|
19
35
|
})
|
|
20
36
|
const innerBlocksProps = useInnerBlocksProps({className: options.containerClassName}, {
|
|
@@ -15,6 +15,41 @@ export interface FluidDecoration {
|
|
|
15
15
|
height: string
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
export type DecorationImage = FluidDecorationImage
|
|
19
|
+
|
|
20
|
+
export type DecorationPosition = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right' | 'center-left' | 'center-right'
|
|
21
|
+
export type DecorationDepth = 'front' | 'under'
|
|
22
|
+
export type DecorationTranslate = 'in' | 'center' | 'out'
|
|
23
|
+
|
|
24
|
+
export interface Decoration {
|
|
25
|
+
img?: DecorationImage
|
|
26
|
+
position?: DecorationPosition
|
|
27
|
+
depth?: DecorationDepth
|
|
28
|
+
translate?: DecorationTranslate
|
|
29
|
+
maxWidth?: string
|
|
30
|
+
offsetX?: string
|
|
31
|
+
offsetY?: string
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export type BackgroundImageFit = 'cover' | 'contain'
|
|
35
|
+
|
|
36
|
+
export type BackgroundImagePosition =
|
|
37
|
+
| 'left top' | 'center top' | 'right top'
|
|
38
|
+
| 'left center' | 'center center' | 'right center'
|
|
39
|
+
| 'left bottom' | 'center bottom' | 'right bottom'
|
|
40
|
+
|
|
41
|
+
export interface BackgroundImageOverlay {
|
|
42
|
+
color?: string
|
|
43
|
+
opacity?: number
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface BackgroundImage {
|
|
47
|
+
img?: FluidDecorationImage
|
|
48
|
+
fit?: BackgroundImageFit
|
|
49
|
+
position?: BackgroundImagePosition
|
|
50
|
+
overlay?: BackgroundImageOverlay
|
|
51
|
+
}
|
|
52
|
+
|
|
18
53
|
export interface BaseBlockAttributes extends Record<string, unknown> {
|
|
19
54
|
id: string
|
|
20
55
|
spacing: {
|
|
@@ -23,15 +58,17 @@ export interface BaseBlockAttributes extends Record<string, unknown> {
|
|
|
23
58
|
}
|
|
24
59
|
background: string
|
|
25
60
|
textColor: string
|
|
61
|
+
decoration: Decoration
|
|
26
62
|
decorations: {
|
|
27
63
|
fluid?: {
|
|
28
64
|
top?: FluidDecoration
|
|
29
65
|
bottom?: FluidDecoration
|
|
30
66
|
}
|
|
31
67
|
}
|
|
68
|
+
backgroundImage: BackgroundImage
|
|
32
69
|
}
|
|
33
70
|
|
|
34
|
-
export type ExcludableBaseAttr = 'spacing' | 'background' | 'id' | 'decorations'
|
|
71
|
+
export type ExcludableBaseAttr = 'spacing' | 'background' | 'id' | 'decorations' | 'decoration' | 'backgroundImage'
|
|
35
72
|
|
|
36
73
|
export interface BaseSectionOptions<TExtra extends Record<string, unknown> = Record<string, never>> extends BriloBlockType {
|
|
37
74
|
supports?: BlockConfiguration['supports']
|