PluginProbe
Content Blocks Builder – Create blocks, repeater blocks with carousel, grid, popup layouts / trunk
Content Blocks Builder – Create blocks, repeater blocks with carousel, grid, popup layouts vtrunk
2.8.14 2.8.13 2.8.12 2.7.5 2.7.6 2.7.7 2.7.8 2.7.9 2.8.0 2.8.1 2.8.10 2.8.11 2.8.2 2.8.3 2.8.4 2.8.5 2.8.6 2.8.7 2.8.8 2.8.9 trunk 1.0.1 1.0.2 1.1.0 1.1.1 All 147 releases
content-blocks-builder / src / block-library / utils.js

utils.js in Content Blocks Builder – Create blocks, repeater blocks with carousel, grid, popup layouts trunk, at src/block-library/utils.js

590 lines 13.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * External dependencies
3 */
4 import { isObject, isEmpty, noop } from "lodash";
5 import fuzzysort from "fuzzysort";
6
7 /**
8 * WordPress dependencies
9 */
10 import { sprintf, __ } from "@wordpress/i18n";
11 import { createContext, useMemo, useReducer } from "@wordpress/element";
12 import { useSelect } from "@wordpress/data";
13 import apiFetch from "@wordpress/api-fetch";
14
15 /**
16 * Internal dependencies
17 */
18 import { store as libraryDataStore } from "./store";
19 import { SearchParams } from "../utils/searchparams";
20
21 // Modal context
22 export const LibraryContext = createContext();
23
24 // Cache key prefix
25 export const cachedLibraryStateKey = "bb-library-state";
26
27 const getInitialKewwords = () => {
28 let keywords = new SearchParams().get("keywordIds", "");
29 if (keywords) {
30 keywords = keywords.split(",").map((id) => ({ id: parseInt(id) }));
31 } else {
32 keywords = [];
33 }
34
35 return keywords;
36 };
37
38 // Shared initial state
39 export const sharedInitialState = {
40 pageSize: 6,
41 currentPage: 1,
42 sortType: "featured",
43 selectedKeywords: getInitialKewwords(),
44 isOpenHelp: false,
45 searchTerm: new SearchParams().get("s", ""),
46 insertingItem: "",
47 isReloading: false,
48 installingPlugins: [],
49 activatingPlugins: [],
50 };
51
52 export const libraryReducer = (state, action) => {
53 switch (action.type) {
54 case "UPDATE_STATE": {
55 return { ...state, ...action.payload };
56 }
57
58 default:
59 return state;
60 }
61 };
62
63 export const dispatchLibraryState = (dispatch, payload) =>
64 dispatch({
65 type: "UPDATE_STATE",
66 payload,
67 });
68
69 export const filterItemsByKeywords = (keywords, items) => {
70 let filteredItems = [];
71 if (!keywords.length || !items.length) {
72 return items;
73 }
74
75 const [firstKeyword, ...restKeywords] = keywords;
76 filteredItems = items.filter(
77 ({ keywordIds: keywords }) => keywords.indexOf(firstKeyword.id) > -1,
78 );
79
80 return filterItemsByKeywords(restKeywords, filteredItems);
81 };
82
83 const searchOptions = {
84 limit: 60,
85 threshold: -100,
86 keys: ["title", "keywords", "description"],
87 };
88
89 export const searchItems = (searchTerm, items) => {
90 let searchedItems = [];
91 if (!searchTerm || searchTerm.length < 3) {
92 searchedItems = items;
93 } else {
94 if (searchTerm.startsWith("id:")) {
95 const searchItemId = parseInt(searchTerm.replace("id:", ""));
96 if (!isNaN(searchItemId) && searchItemId) {
97 searchedItems = items.filter(({ id }) => id === searchItemId);
98 }
99 } else {
100 const results = fuzzysort.go(searchTerm, items, searchOptions);
101 if (results.length) {
102 searchedItems = results.map(({ obj }) => obj);
103 }
104 }
105 }
106
107 return searchedItems;
108 };
109
110 const sortItemCb = (
111 sortType,
112 args = { "30_days": "count_30", "7_days": "count_7" },
113 ) => {
114 let cb;
115
116 if (sortType === "featured") {
117 cb = (a, b) => b.order - a.order;
118 } else if (sortType === "latest") {
119 cb = (a, b) => b.id - a.id;
120 } else if (sortType === "30_days") {
121 cb = (a, b) => b.meta[args["30_days"]] - a.meta[args["30_days"]];
122 } else if (sortType === "7_days") {
123 cb = (a, b) => b.meta[args["7_days"]] - a.meta[args["7_days"]];
124 }
125 return cb;
126 };
127
128 // Build content the library page
129 export const useLibraryContent = (libraryState, items, loadFullItems) => {
130 const { pageSize, currentPage, sortType, selectedKeywords, searchTerm } =
131 libraryState;
132
133 // Filter by keywords
134 const keywordIds = selectedKeywords.join(",");
135 const filteredItems = useMemo(() => {
136 return filterItemsByKeywords(selectedKeywords, items);
137 }, [keywordIds, items]);
138
139 // Search
140 const searchedItems = useMemo(() => {
141 // Only sorting when no search.
142 if (searchTerm && searchTerm.length >= 3) {
143 return searchItems(searchTerm, filteredItems);
144 }
145
146 return filteredItems;
147 }, [searchTerm, filteredItems]);
148
149 // Sorting
150 const sortedItems = useMemo(() => {
151 return searchedItems.sort(sortItemCb(sortType));
152 }, [searchedItems, sortType]);
153
154 let currentPageItems = sortedItems.slice(
155 (currentPage - 1) * pageSize,
156 pageSize * currentPage,
157 );
158
159 currentPageItems = currentPageItems.map((item) => {
160 return { ...item, loadingFullData: !item?.slug };
161 });
162
163 let itemIds = currentPageItems
164 .map(({ id, loadingFullData }) => (loadingFullData ? id : false))
165 .filter((i) => i);
166
167 const [, forceRender] = useReducer(() => ({}));
168 if (itemIds.length) {
169 const result = loadFullItems(itemIds);
170 result.then(() => {
171 forceRender();
172 });
173 }
174
175 return [currentPageItems, sortedItems];
176 };
177
178 // Get library URL by content type
179 export const getLibraryURL = (contentType = "block") => {
180 let url;
181 switch (contentType) {
182 case "block":
183 url = window?.CBBBlockLibrary?.URL;
184 break;
185
186 case "variation":
187 url = window?.CBBVariationLibrary?.URL;
188 break;
189
190 case "pattern":
191 url = window?.CBBPatternLibrary?.URL;
192 break;
193
194 default:
195 url = "https://boldpatterns.net";
196 break;
197 }
198
199 if (!url) {
200 url = "https://boldpatterns.net";
201 }
202
203 return url;
204 };
205
206 // Parse blocks from raw data
207 export const refinePreviewData = (rawBlocks) => {
208 rawBlocks = (rawBlocks ?? "").replaceAll(
209 /\w+:\/\/\S*(w=(\d*))&(h=(\d*))&\w+\S*"/g,
210 (url, w, width, h, height) => {
211 if (width > 800) {
212 return url
213 .replace(w, "w=" + Math.floor(Number(width) / 2))
214 .replace(h, "h=" + Math.floor(Number(height) / 2));
215 }
216
217 return url;
218 },
219 );
220
221 let blocks;
222 if (rawBlocks) {
223 blocks = JSON.parse(rawBlocks);
224
225 if (!!blocks.length) {
226 const { attributes: { boldblocks: { height } = {} } = {} } =
227 blocks[0] ?? {};
228
229 if (isObject(height) && !isEmpty(height)) {
230 let { lg: { value: { value } } = {} } = height;
231
232 if (value === "100vh") {
233 blocks[0] = {
234 ...blocks[0],
235 attributes: {
236 ...blocks[0].attributes,
237 boldblocks: {
238 ...blocks[0].attributes.boldblocks,
239 height: {
240 ...height,
241 lg: { value: { height: "96vh", value: "96vh" } },
242 },
243 },
244 },
245 };
246 }
247 }
248 }
249 }
250 return blocks;
251 };
252
253 export const findExistVariation = (slug, localVariations) =>
254 localVariations.find((item) => slug === item.slug);
255
256 const buildBlocksFromInnerTemplate = (blocks) => {
257 let blockData = [];
258 if (!blocks.length) {
259 return blockData;
260 }
261
262 for (let i = 0; i < blocks.length; i++) {
263 let [name, attributes, innerBlocks] = blocks[i];
264 innerBlocks = buildBlocksFromInnerTemplate(innerBlocks);
265
266 blockData.push({ name, attributes, innerBlocks });
267 }
268
269 return blockData;
270 };
271
272 export const getBlocksFromVariationData = (variationDataRaw) => {
273 if (!variationDataRaw) {
274 return [];
275 }
276
277 const variationData = JSON.parse(variationDataRaw);
278
279 const {
280 blockName: name,
281 variation: { attributes, innerBlocks },
282 } = variationData;
283
284 return [
285 {
286 name,
287 attributes,
288 innerBlocks: buildBlocksFromInnerTemplate(innerBlocks),
289 },
290 ];
291 };
292
293 export const importData = ({
294 contentType,
295 item,
296 existingId = false,
297 localVariations,
298 finishCallback,
299 }) => {
300 if (contentType === "block") {
301 importBlock({ item, existingId, localVariations, finishCallback });
302 } else {
303 importVariation({ item, existingId, finishCallback });
304 }
305 };
306
307 export const importBlock = async ({
308 item,
309 existingId = false,
310 localVariations,
311 finishCallback = noop,
312 }) => {
313 const {
314 title,
315 slug,
316 content,
317 keywords,
318 variations,
319 parentVariations,
320 meta: {
321 boldblocks_is_pro,
322 boldblocks_has_pro_features,
323 boldblocks_download_count,
324 boldblocks_download_7_count,
325 boldblocks_download_30_count,
326 boldblocks_download_stats,
327 boldblocks_tutorials,
328 boldblocks_external_resources,
329 is_pro,
330 has_pro_features,
331 download_count,
332 download_7_count,
333 download_30_count,
334 download_stats,
335 tutorials,
336 external_resources,
337 ...meta
338 } = {},
339 } = item;
340
341 const block = {
342 title,
343 slug,
344 content,
345 meta,
346 keywords,
347 };
348
349 const variationData = [];
350
351 if (variations && variations?.length) {
352 variationData.push(
353 ...variations.map(({ title, content, slug, meta }) => ({
354 title,
355 content,
356 slug,
357 meta,
358 })),
359 );
360 }
361
362 if (parentVariations && parentVariations?.length) {
363 variationData.push(
364 ...parentVariations.map(({ title, content, slug, meta }) => ({
365 title,
366 content,
367 slug,
368 meta,
369 })),
370 );
371 }
372
373 let messages = [];
374
375 return apiFetch({
376 path: existingId
377 ? `wp/v2/boldblocks-blocks/${existingId}`
378 : "wp/v2/boldblocks-blocks",
379 method: "POST",
380 data: { ...block, status: "publish" },
381 })
382 .then((res) => {
383 messages.push({
384 id: res.id,
385 slug: block.slug,
386 type: "success",
387 message: sprintf(
388 __(
389 "The block '%s' has been imported successfully.",
390 "content-blocks-builder",
391 ),
392 block.title,
393 ),
394 });
395 if (variationData.length) {
396 Promise.all(
397 variationData.map(async (item) => {
398 const existVariation = findExistVariation(
399 item.slug,
400 localVariations,
401 );
402 return apiFetch({
403 path: !existVariation
404 ? "boldblocks/v1/createVariation"
405 : `wp/v2/boldblocks-variations/${existVariation.id}`,
406 method: "POST",
407 data: {
408 ...item,
409 status: "publish",
410 ...(!existVariation
411 ? { cbb_variation_nonce: CBBBlocks?.variationNonce }
412 : {}),
413 },
414 })
415 .then((res) => ({
416 id: existVariation ? res.id : res?.post?.id,
417 slug: item.slug,
418 type: "success",
419 message: sprintf(
420 __(
421 "The variation '%s' has been imported successfully.",
422 "content-blocks-builder",
423 ),
424 item.title,
425 ),
426 }))
427 .catch((error) => {
428 console.error(error);
429 return {
430 slug: item.slug,
431 type: "error",
432 message: sprintf(
433 __(
434 "Failed to import variation: '%s'.",
435 "content-blocks-builder",
436 ),
437 item.title,
438 ),
439 };
440 });
441 }),
442 ).then((res) => {
443 messages.push(...res);
444
445 finishCallback(messages);
446 });
447 } else {
448 finishCallback(messages);
449 }
450
451 return messages;
452 })
453 .catch((error) => {
454 console.error(error);
455
456 messages.push({
457 slug: block.slug,
458 type: "error",
459 message: sprintf(
460 __("Failed to import block: '%s'.", "content-blocks-builder"),
461 block.title,
462 ),
463 });
464
465 finishCallback(messages);
466
467 return messages;
468 });
469 };
470
471 export const importVariation = async ({
472 item,
473 existingId = false,
474 finishCallback = noop,
475 }) => {
476 const {
477 title,
478 slug,
479 content,
480 meta: {
481 boldblocks_is_pro,
482 boldblocks_has_pro_features,
483 boldblocks_download_count,
484 boldblocks_download_7_count,
485 boldblocks_download_30_count,
486 boldblocks_download_stats,
487 boldblocks_tutorials,
488 boldblocks_external_resources,
489 is_pro,
490 has_pro_features,
491 download_count,
492 download_7_count,
493 download_30_count,
494 download_stats,
495 tutorials,
496 external_resources,
497 boldblocks_is_queryable,
498 ...meta
499 } = {},
500 } = item;
501
502 const variation = {
503 title,
504 slug,
505 content,
506 meta,
507 };
508
509 let messages = [];
510
511 return apiFetch({
512 path: existingId
513 ? `wp/v2/boldblocks-variations/${existingId}`
514 : "boldblocks/v1/createVariation",
515 method: "POST",
516 data: {
517 ...variation,
518 status: "publish",
519 ...(!existingId
520 ? { cbb_variation_nonce: CBBBlocks?.variationNonce }
521 : {}),
522 },
523 })
524 .then((res) => {
525 messages.push({
526 id: existingId ? res.id : res?.post?.id,
527 slug: variation.slug,
528 type: "success",
529 message: sprintf(
530 __(
531 "The variation '%s' has been imported successfully.",
532 "content-blocks-builder",
533 ),
534 variation.title,
535 ),
536 });
537
538 finishCallback(messages);
539
540 return messages;
541 })
542 .catch((error) => {
543 console.error(error);
544
545 messages.push({
546 slug: variation.slug,
547 type: "error",
548 message: sprintf(
549 __("Failed to import variation: '%s'.", "content-blocks-builder"),
550 variation.title,
551 ),
552 });
553
554 finishCallback(messages);
555
556 return messages;
557 });
558 };
559
560 /**
561 * Decode html
562 *
563 * @param {String} html
564 * @returns {String}
565 */
566 export const decodeHtml = (html) => {
567 var txt = document.createElement("textarea");
568 txt.innerHTML = html;
569 return txt.value;
570 };
571
572 export const getIsResolving = (
573 selector,
574 args = [],
575 store = libraryDataStore,
576 ) => {
577 return useSelect((select) => select(store).isResolving(selector, args), []);
578 };
579
580 export const getHasFinishedResolution = (
581 selector,
582 args = [],
583 store = libraryDataStore,
584 ) => {
585 return useSelect(
586 (select) => select(store).hasFinishedResolution(selector, args),
587 [],
588 );
589 };
590