PluginProbe
Block Animations, Motion & Scroll Effects – Ghost Kit / 3.3.2
Block Animations, Motion & Scroll Effects – Ghost Kit v3.3.2
3.7.2 3.7.1 3.7.0 3.6.1 trunk 1.6.3 2.25.0 3.3.0 3.3.1 3.3.2 3.3.3 3.4.0 3.4.1 3.4.2 3.4.3 3.4.4 3.4.5 3.4.6 3.5.0 3.5.1 3.6.0
ghostkit / gutenberg / extend / block-actions-copy-paste / use-paste-extensions.js

use-paste-extensions.js in Block Animations, Motion & Scroll Effects – Ghost Kit 3.3.2, at gutenberg/extend/block-actions-copy-paste/use-paste-extensions.js

254 lines 6.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import { cloneDeep } from 'lodash';
2
3 import { getBlockType, hasBlockSupport, parse } from '@wordpress/blocks';
4 import { useDispatch, useRegistry } from '@wordpress/data';
5 import { useCallback } from '@wordpress/element';
6 import { __, sprintf } from '@wordpress/i18n';
7 import { store as noticesStore } from '@wordpress/notices';
8
9 import compactObject from '../../utils/compact-object';
10 import merge from '../../utils/merge';
11 import { getSpecificPropsFromStyles } from '../../utils/styles';
12 import { EXTENSIONS } from '../constants';
13
14 /**
15 * Determine if the copied text looks like serialized blocks or not.
16 * Since plain text will always get parsed into a freeform block,
17 * we check that if the parsed blocks is anything other than that.
18 *
19 * @param {string} text The copied text.
20 * @return {boolean} True if the text looks like serialized blocks, false otherwise.
21 */
22 function hasSerializedBlocks(text) {
23 try {
24 const blocks = parse(text, {
25 __unstableSkipMigrationLogs: true,
26 __unstableSkipAutop: true,
27 });
28 if (blocks.length === 1 && blocks[0].name === 'core/freeform') {
29 // It's likely that the text is just plain text and not serialized blocks.
30 return false;
31 }
32 return true;
33 } catch (err) {
34 // Parsing error, the text is not serialized blocks.
35 // (Even though that it technically won't happen)
36 return false;
37 }
38 }
39
40 /**
41 * Get the "ghostkit attributes" from a given block to a target block.
42 *
43 * @param {WPBlock} sourceBlock The source block.
44 * @param {WPBlock} targetBlock The target block.
45 * @param {string} selectedExtension Copy the specific extension data.
46 *
47 * @return {Object} the filtered attributes object.
48 */
49 function getGhostkitAttributes(
50 sourceBlock,
51 targetBlock,
52 selectedExtension = ''
53 ) {
54 // Copy all extensions.
55 if (!selectedExtension) {
56 return {
57 ghostkit: cloneDeep(sourceBlock?.attributes?.ghostkit || {}),
58 };
59 }
60
61 const attributes = {};
62
63 // Only apply the attribute if both blocks support it.
64 if (
65 hasBlockSupport(sourceBlock.name, ['ghostkit', selectedExtension]) &&
66 hasBlockSupport(targetBlock.name, ['ghostkit', selectedExtension])
67 ) {
68 attributes.ghostkit = cloneDeep(
69 targetBlock?.attributes?.ghostkit || {}
70 );
71
72 // Extension with styles.
73 if (EXTENSIONS[selectedExtension]?.styles) {
74 if (!attributes?.ghostkit?.styles) {
75 attributes.ghostkit.styles = {};
76 }
77
78 attributes.ghostkit.styles = merge(
79 attributes.ghostkit.styles,
80 getSpecificPropsFromStyles(
81 sourceBlock?.attributes?.ghostkit?.styles || {},
82 EXTENSIONS[selectedExtension].styles,
83 EXTENSIONS[selectedExtension].responsive,
84 EXTENSIONS[selectedExtension].selectors
85 )
86 );
87
88 attributes.ghostkit.styles = compactObject(
89 attributes.ghostkit.styles
90 );
91 }
92
93 // Extension with attributes.
94 if (EXTENSIONS[selectedExtension]?.attributes) {
95 EXTENSIONS[selectedExtension].attributes.forEach((attrName) => {
96 attributes.ghostkit[attrName] =
97 sourceBlock?.attributes?.ghostkit?.[attrName] || undefined;
98 });
99 }
100 }
101
102 return attributes;
103 }
104
105 /**
106 * Update the target blocks with extensions attributes recursively.
107 *
108 * @param {WPBlock[]} targetBlocks The target blocks to be updated.
109 * @param {WPBlock[]} sourceBlocks The source blocks to get extensions attributes from.
110 * @param {string} selectedExtension Copy the specific extension data.
111 *
112 * @param {Function} updateBlockAttributes The function to update the attributes.
113 */
114 function recursivelyUpdateBlockAttributes(
115 targetBlocks,
116 sourceBlocks,
117 updateBlockAttributes,
118 selectedExtension = ''
119 ) {
120 for (
121 let index = 0;
122 index < Math.min(sourceBlocks.length, targetBlocks.length);
123 index += 1
124 ) {
125 updateBlockAttributes(
126 targetBlocks[index].clientId,
127 getGhostkitAttributes(
128 sourceBlocks[index],
129 targetBlocks[index],
130 selectedExtension
131 )
132 );
133
134 recursivelyUpdateBlockAttributes(
135 targetBlocks[index].innerBlocks,
136 sourceBlocks[index].innerBlocks,
137 updateBlockAttributes,
138 selectedExtension
139 );
140 }
141 }
142
143 /**
144 * A hook to return a pasteExtensions event function for handling pasting extensions to blocks.
145 *
146 * @return {Function} A function to update the extensions to the blocks.
147 */
148 export default function usePasteExtensions() {
149 const registry = useRegistry();
150 const { updateBlockAttributes } = useDispatch('core/block-editor');
151 const { createSuccessNotice, createWarningNotice, createErrorNotice } =
152 useDispatch(noticesStore);
153
154 return useCallback(
155 async (targetBlocks, selectedExtension) => {
156 let html = '';
157 try {
158 // `http:` sites won't have the clipboard property on navigator.
159 // (with the exception of localhost.)
160 if (!window.navigator.clipboard) {
161 createErrorNotice(
162 __(
163 'Unable to paste extensions. This feature is only available on secure (https) sites in supporting browsers.',
164 'ghostkit'
165 ),
166 { type: 'snackbar' }
167 );
168 return;
169 }
170
171 html = await window.navigator.clipboard.readText();
172 } catch (error) {
173 // Possibly the permission is denied.
174 createErrorNotice(
175 __(
176 'Unable to paste extensions. Please allow browser clipboard permissions before continuing.',
177 'ghostkit'
178 ),
179 {
180 type: 'snackbar',
181 }
182 );
183 return;
184 }
185
186 // Abort if the copied text is empty or doesn't look like serialized blocks.
187 if (!html || !hasSerializedBlocks(html)) {
188 createWarningNotice(
189 __(
190 "Unable to paste extensions. Block extensions couldn't be found within the copied content.",
191 'ghostkit'
192 ),
193 {
194 type: 'snackbar',
195 }
196 );
197 return;
198 }
199
200 const copiedBlocks = parse(html);
201
202 if (copiedBlocks.length === 1) {
203 // Apply extensions of the block to all the target blocks.
204 registry.batch(() => {
205 recursivelyUpdateBlockAttributes(
206 targetBlocks,
207 targetBlocks.map(() => copiedBlocks[0]),
208 updateBlockAttributes,
209 selectedExtension
210 );
211 });
212 } else {
213 registry.batch(() => {
214 recursivelyUpdateBlockAttributes(
215 targetBlocks,
216 copiedBlocks,
217 updateBlockAttributes,
218 selectedExtension
219 );
220 });
221 }
222
223 if (targetBlocks.length === 1) {
224 const title = getBlockType(targetBlocks[0].name)?.title;
225 createSuccessNotice(
226 sprintf(
227 // Translators: Name of the block being pasted, e.g. "Paragraph".
228 __('Pasted extensions to %s.'),
229 title
230 ),
231 { type: 'snackbar' }
232 );
233 } else {
234 createSuccessNotice(
235 sprintf(
236 // Translators: The number of the blocks.
237 __('Pasted extensions to %d blocks.'),
238 targetBlocks.length
239 ),
240 { type: 'snackbar' }
241 );
242 }
243 },
244 // eslint-disable-next-line react-hooks/exhaustive-deps
245 [
246 registry.batch,
247 updateBlockAttributes,
248 createSuccessNotice,
249 createWarningNotice,
250 createErrorNotice,
251 ]
252 );
253 }
254