@secretstache/wordpress-gutenberg 0.2.5 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. package/README.md +4 -0
  2. package/build/index.js +8 -2
  3. package/build/index.js.map +1 -1
  4. package/build/{index.css → styles.css} +44 -10
  5. package/package.json +5 -3
  6. package/src/components/ColorPaletteControl.js +24 -0
  7. package/src/components/DataQueryControls.js +51 -0
  8. package/src/components/DividersControl.js +74 -0
  9. package/src/components/IconPicker.js +73 -0
  10. package/src/components/ImageActions.js +59 -0
  11. package/src/components/LinkControl.js +30 -0
  12. package/src/components/MediaPicker.js +179 -0
  13. package/src/components/MediaTypeControl.js +54 -0
  14. package/src/components/ResourcesWrapper.js +46 -0
  15. package/src/components/ResponsiveSpacingControl.js +74 -0
  16. package/src/components/SortableSelect.js +60 -0
  17. package/src/components/SpacingControl.js +119 -0
  18. package/src/components/index.js +12 -0
  19. package/src/hooks/index.js +11 -0
  20. package/src/hooks/useAccordionItem.js +51 -0
  21. package/src/hooks/useAllowedBlocks.js +25 -0
  22. package/src/hooks/useBlockTabsData.js +90 -0
  23. package/src/hooks/useChildBlockPosition.js +31 -0
  24. package/src/hooks/useColorChange.js +12 -0
  25. package/src/hooks/useDataQuery.js +45 -0
  26. package/src/hooks/useParentBlock.js +57 -0
  27. package/src/hooks/usePreviewToggle.js +32 -0
  28. package/src/hooks/useSlider.js +24 -0
  29. package/src/hooks/useThemeColors.js +19 -0
  30. package/src/hooks/useUpdateAttribute.js +4 -0
  31. package/src/index.js +6 -0
  32. package/src/styles/_animation-file-renderer.scss +11 -0
  33. package/src/styles/_editor-base.scss +56 -0
  34. package/src/styles/_icon-picker.scss +4 -0
  35. package/src/styles/_image-wrapper.scss +59 -0
  36. package/src/styles/_link-control.scss +6 -0
  37. package/src/styles/_media-picker.scss +20 -0
  38. package/src/styles/_new-child-btn.scss +15 -0
  39. package/src/styles/_responsive-spacing.scss +30 -0
  40. package/src/styles/_root-block-appender.scss +40 -0
  41. package/src/styles/_sortable-select.scss +5 -0
  42. package/src/styles/styles.scss +12 -0
  43. package/src/utils/attributes.js +224 -0
  44. package/src/utils/constants.js +17 -0
  45. package/src/utils/helpers.js +175 -0
  46. package/src/utils/index.js +6 -0
  47. package/src/utils/rootBlock/README.md +71 -0
  48. package/src/utils/rootBlock/hideRootBlockForInlineInserter.js +13 -0
  49. package/src/utils/rootBlock/hideRootBlockForOtherBlocks.js +32 -0
  50. package/src/utils/rootBlock/index.js +4 -0
  51. package/src/utils/rootBlock/initRootBlockAppender.js +45 -0
  52. package/src/utils/rootBlock/setRootBlock.js +32 -0
  53. package/src/utils/waitForContainer/README.md +40 -0
  54. package/src/utils/waitForContainer/index.js +25 -0
@@ -0,0 +1,175 @@
1
+ import apiFetch from '@wordpress/api-fetch';
2
+ import slugify from 'slugify';
3
+ import classNames from 'classnames';
4
+
5
+ export const loadSelectOptions = async (inputValue, postType, mapper = null) => {
6
+ const response = await apiFetch({
7
+ path: `/wp/v2/${postType}?search=${encodeURIComponent(inputValue)}`,
8
+ });
9
+
10
+ if (mapper) {
11
+ return response?.map(mapper);
12
+ } else {
13
+ return response?.map((post) => {
14
+ // Create a temporary DOM element to decode HTML entities
15
+ const tempElement = document.createElement('div');
16
+ tempElement.innerHTML = post?.title?.rendered;
17
+
18
+ return {
19
+ value: post.id,
20
+ label: tempElement.textContent || tempElement.innerText || '',
21
+ };
22
+ });
23
+ }
24
+ };
25
+
26
+ export const getSlug = (name) => slugify(name, {
27
+ replacement: '-',
28
+ lower: true,
29
+ strict: true,
30
+ });
31
+
32
+ export const cleanSvgString = (svgString) => {
33
+ if (svgString.startsWith('<?xml')) {
34
+ const endOfXml = svgString.indexOf('?>');
35
+
36
+ if (endOfXml !== -1) {
37
+ svgString = svgString.substring(endOfXml + 2);
38
+ }
39
+ }
40
+
41
+ svgString = svgString.trim();
42
+
43
+ return svgString;
44
+ };
45
+
46
+
47
+ const SVG_MIME_TYPE = 'image/svg+xml';
48
+
49
+ export const getImage = async (mediaData) => {
50
+ const isSvg = mediaData?.mime === SVG_MIME_TYPE || mediaData?.mime_type === SVG_MIME_TYPE;
51
+ const imagePayload = {
52
+ isSvg,
53
+ imageUrl: null,
54
+ svgCode: null,
55
+ width: mediaData?.width,
56
+ height: mediaData?.height,
57
+ };
58
+
59
+ if (isSvg) {
60
+ imagePayload.svgCode = await fetch(mediaData.url).then(response => response.text()).then(cleanSvgString);
61
+ } else {
62
+ imagePayload.imageUrl = mediaData?.url;
63
+ }
64
+
65
+ return imagePayload;
66
+ };
67
+
68
+ export const getPhoneNumber = (phone) => {
69
+ if (!phone) return '';
70
+
71
+ let formatted = phone;
72
+
73
+ const pattern = /^\+\d(\d{3})(\d{3})(\d{4})$/;
74
+ const match = phone.match(pattern);
75
+
76
+ if (match) {
77
+ formatted = `${match[1]}-${match[2]}-${match[3]}`;
78
+ }
79
+
80
+ return formatted;
81
+ };
82
+
83
+ export const getLocationAddress = (location) => {
84
+ const {
85
+ street_number = '',
86
+ street_name = '',
87
+ city = '',
88
+ state_short = '',
89
+ post_code = '',
90
+ } = location;
91
+
92
+ // Start with the street number and name, trimming to remove extra spaces if one is missing
93
+ let addressParts = [`${street_number} ${street_name}`.trim()];
94
+
95
+ // Add the city with a line break, but only if there is a city name.
96
+ if (city) {
97
+ addressParts.push(`<br>${city}`);
98
+ }
99
+
100
+ // Combine state and postal code intelligently, adding only if they exist
101
+ let statePostalParts = [];
102
+ if (state_short) statePostalParts.push(state_short);
103
+ if (post_code) statePostalParts.push(post_code);
104
+ let statePostal = statePostalParts.join(' ');
105
+
106
+ if (statePostal) {
107
+ // Add a comma only if there's something before this part
108
+ addressParts.push(`${addressParts.length > 0 ? ', ' : ''}${statePostal}`);
109
+ }
110
+
111
+ return addressParts.join('');
112
+ };
113
+
114
+ export const decodeHtmlEntities = (text) => {
115
+ const tempElement = document.createElement('div');
116
+ tempElement.innerHTML = text;
117
+
118
+ return tempElement.textContent || tempElement.innerText || '';
119
+ };
120
+
121
+ /**
122
+ * Generates a string of class names for spacing based on the provided spacing object.
123
+ *
124
+ * @param {Object} spacing - The spacing object containing margin and padding values.
125
+ * @param {Object} [desktopPrefixes] - Optional prefixes for desktop spacing classes.
126
+ * @param {string} [desktopPrefixes.marginTop='mt-'] - Prefix for desktop margin-top class.
127
+ * @param {string} [desktopPrefixes.marginBottom='mb-'] - Prefix for desktop margin-bottom class.
128
+ * @param {string} [desktopPrefixes.paddingTop='pt-'] - Prefix for desktop padding-top class.
129
+ * @param {string} [desktopPrefixes.paddingBottom='pb-'] - Prefix for desktop padding-bottom class.
130
+ * @param {Object} [mobilePrefixes] - Optional prefixes for mobile spacing classes.
131
+ * @param {string} [mobilePrefixes.marginTop='sm:mt-'] - Prefix for mobile margin-top class.
132
+ * @param {string} [mobilePrefixes.marginBottom='sm:mb-'] - Prefix for mobile margin-bottom class.
133
+ * @param {string} [mobilePrefixes.paddingTop='sm:pt-'] - Prefix for mobile padding-top class.
134
+ * @param {string} [mobilePrefixes.paddingBottom='sm:pb-'] - Prefix for mobile padding-bottom class.
135
+ * @returns {string} - A string of class names for the specified spacing.
136
+ */
137
+ export const getSpacingClasses = (
138
+ spacing,
139
+ desktopPrefixes = {
140
+ marginTop: 'mt-',
141
+ marginBottom: 'mb-',
142
+ paddingTop: 'pt-',
143
+ paddingBottom: 'pb-',
144
+ },
145
+ mobilePrefixes = {
146
+ marginTop: 'sm:mt-',
147
+ marginBottom: 'sm:mb-',
148
+ paddingTop: 'sm:pt-',
149
+ paddingBottom: 'sm:pb-',
150
+ }
151
+ ) => {
152
+ if (spacing?.desktop || spacing?.mobile) {
153
+ return classNames({
154
+ [`${desktopPrefixes.marginTop}${spacing.desktop.margin.top}`]: spacing.desktop.margin.top !== -1,
155
+ [`${desktopPrefixes.marginBottom}${spacing.desktop.margin.bottom}`]: spacing.desktop.margin.bottom !== -1,
156
+
157
+ [`${desktopPrefixes.paddingTop}${spacing.desktop.padding.top}`]: spacing.desktop.padding.top !== -1,
158
+ [`${desktopPrefixes.paddingBottom}${spacing.desktop.padding.bottom}`]: spacing.desktop.padding.bottom !== -1,
159
+
160
+ [`${mobilePrefixes.marginTop}${spacing.mobile.margin.top}`]: spacing.mobile.margin.top !== -1,
161
+ [`${mobilePrefixes.marginBottom}${spacing.mobile.margin.bottom}`]: spacing.mobile.margin.bottom !== -1,
162
+
163
+ [`${mobilePrefixes.paddingTop}${spacing.mobile.padding.top}`]: spacing.mobile.padding.top !== -1,
164
+ [`${mobilePrefixes.paddingBottom}${spacing.mobile.padding.bottom}`]: spacing.mobile.padding.bottom !== -1,
165
+ });
166
+ } else {
167
+ return classNames({
168
+ [`${desktopPrefixes.marginTop}${spacing.margin.top}`]: spacing.margin.top !== -1,
169
+ [`${desktopPrefixes.marginBottom}${spacing.margin.bottom}`]: spacing.margin.bottom !== -1,
170
+
171
+ [`${desktopPrefixes.paddingTop}${spacing.padding.top}`]: spacing.padding.top !== -1,
172
+ [`${desktopPrefixes.paddingBottom}${spacing.padding.bottom}`]: spacing.padding.bottom !== -1,
173
+ });
174
+ }
175
+ };
@@ -0,0 +1,6 @@
1
+ export * from './rootBlock/index.js';
2
+ export * from './waitForContainer/index.js';
3
+
4
+ export * from './attributes.js';
5
+ export * from './constants.js';
6
+ export * from './helpers.js';
@@ -0,0 +1,71 @@
1
+ ## setRootBlock
2
+
3
+ The `setRootBlock` function configures a root block in the Gutenberg editor and optionally customizes the tooltip text for the root appender.
4
+ This function ensures that only the specified root block can be inserted at the root level.
5
+ Additionally, it allows for optional initialization of the root appender with customizable tooltip text and makes the click on the appender insert the specified block.
6
+
7
+ ### Function Signature
8
+
9
+ ```javascript
10
+ /**
11
+ * Sets the root block in the Gutenberg editor and optionally customizes the tooltip text for the root appender.
12
+ *
13
+ * @param {string} rootBlockName - The name of the block to be set as the root block.
14
+ * @param {boolean} [initAppender=true] - Flag to indicate whether to initialize the root appender.
15
+ * @param {string} [appenderTooltipText='Add Row'] - The tooltip text to be displayed on the root appender.
16
+ */
17
+ export const setRootBlock = (rootBlockName, initAppender = true, appenderTooltipText = 'Add Row');
18
+ ```
19
+
20
+ #### Parameters
21
+
22
+ - **rootBlockName** (`string`): The name of the block to be set as the root block. This parameter is mandatory.
23
+ - **initAppender** (`boolean`, optional): Flag to indicate whether to initialize the root appender. Default is `true`.
24
+ - **appenderTooltipText** (`string`, optional): The tooltip text to be displayed on the appender. Default is 'Add Row'.
25
+
26
+ ### Usage example
27
+
28
+ To use the `setRootBlock` function, import it into your script and pass the necessary parameters:
29
+
30
+ ```javascript
31
+ import { setRootBlock } from '@secretstache/wordpress-gutenberg';
32
+
33
+ setRootBlock('ssm/section-wrapper', true, 'Custom Tooltip Text');
34
+ ```
35
+
36
+ In this example, the function will set `ssm/section-wrapper` as the root block, customize the tooltip text of the appender to 'Custom Tooltip Text', and initialize the root appender.
37
+
38
+ ---
39
+
40
+ ## initRootBlockAppender
41
+
42
+ The `initRootBlockAppender` function creates a new top-level block appender. It allows to specify the block name to be
43
+ created when the appender is clicked and customize the tooltip text displayed on the appender.
44
+
45
+ ### Function Signature
46
+
47
+ ```javascript
48
+ /**
49
+ * Creates a new top-level block appender. It allows to specify the block name to be
50
+ * created when the appender is clicked and customize the tooltip text displayed on the appender.
51
+ *
52
+ * @param {string} blockName - The name of the block to be created when the appender is clicked.
53
+ * @param {string} [tooltipText='Add Row'] - The tooltip text displayed on the appender.
54
+ */
55
+ export const initRootBlockAppender = (blockName, tooltipText = 'Add Row');
56
+ ```
57
+
58
+ #### Parameters
59
+
60
+ - **blockName** (`string`): The name of the block to be created when the appender is clicked. This parameter is mandatory.
61
+ - **tooltipText** (`string`, optional): The tooltip text displayed on the appender. Default is 'Add Row'.
62
+
63
+ ### Usage example
64
+
65
+ In your custom theme, you can import and initialize the function with the desired block name and tooltip text:
66
+
67
+ ```javascript
68
+ import { initRootBlockAppender } from '@secretstache/wordpress-gutenberg';
69
+
70
+ initRootBlockAppender('ssm/section-wrapper', 'Add Row');
71
+ ```
@@ -0,0 +1,13 @@
1
+ export const hideRootBlockForInlineInserter = (blockName) => {
2
+ // Construct the CSS rule
3
+ const cssRule = `[id*="-block-${blockName}"] { display: none !important; }`;
4
+
5
+ // Create a style element
6
+ const styleElement = document.createElement('style');
7
+
8
+ // Set the CSS rule as the content of the style element
9
+ styleElement.appendChild(document.createTextNode(cssRule));
10
+
11
+ // Append the style element to the document's head
12
+ document.head.appendChild(styleElement);
13
+ };
@@ -0,0 +1,32 @@
1
+ import { addFilter } from '@wordpress/hooks';
2
+ import { getBlockTypes } from '@wordpress/blocks';
3
+ import { dispatch } from '@wordpress/data';
4
+
5
+ export const hideRootBlockForOtherBlocks = (rootBlockName) => {
6
+ addFilter(
7
+ 'blocks.registerBlockType',
8
+ 'ssm/with-root-block',
9
+ (blockSettings, blockName) => {
10
+ const isRootBlock = blockName === rootBlockName;
11
+ const hasOwnAllowedBlocks = blockSettings?.allowedBlocks;
12
+
13
+ if (isRootBlock || hasOwnAllowedBlocks) {
14
+ return blockSettings;
15
+ }
16
+
17
+ // get all blockTypes
18
+ blockSettings.allowedBlocks = getBlockTypes()
19
+ ?.filter((allowedBlock) => {
20
+ const isRootBlock = allowedBlock.name === rootBlockName;
21
+ const hasParent = !!allowedBlock?.parent;
22
+
23
+ return !isRootBlock && !hasParent;
24
+ })
25
+ ?.map(allowedBlock => allowedBlock.name);
26
+
27
+ return blockSettings;
28
+ },
29
+ );
30
+
31
+ dispatch('core/blocks').reapplyBlockTypeFilters();
32
+ };
@@ -0,0 +1,4 @@
1
+ export * from './setRootBlock.js';
2
+ export * from './initRootBlockAppender.js';
3
+ export * from './hideRootBlockForInlineInserter.js';
4
+ export * from './hideRootBlockForOtherBlocks.js';
@@ -0,0 +1,45 @@
1
+ import { createBlock } from '@wordpress/blocks';
2
+ import { dispatch } from '@wordpress/data';
3
+ import domReady from '@wordpress/dom-ready';
4
+
5
+ import { waitForContainer } from '../waitForContainer/index.js';
6
+
7
+ const ROOT_CONTAINER_SELECTOR = '.is-root-container';
8
+
9
+ /**
10
+ * Initializes the custom button for the root appender.
11
+ * @param {string} blockName - The name of the block to be created when the appender is clicked.
12
+ * @param {string} tooltipText - The tooltip text displayed on the appender.
13
+ */
14
+ const initialize = (blockName, tooltipText) => {
15
+ const rootContainer = document.querySelector(ROOT_CONTAINER_SELECTOR);
16
+ if (!rootContainer) {
17
+ console.error('Root container not found');
18
+
19
+ return;
20
+ }
21
+
22
+ const button = document.createElement('button');
23
+
24
+ button.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" aria-hidden="true" focusable="false"><path d="M11 12.5V17.5H12.5V12.5H17.5V11H12.5V6H11V11H6V12.5H11Z"></path></svg>';
25
+ button.className = 'components-button block-editor-button-block-appender root-block-appender';
26
+ button.setAttribute('aria-label', tooltipText);
27
+ button.setAttribute('data-tooltip', tooltipText);
28
+
29
+ button.addEventListener('click', () => {
30
+ dispatch('core/block-editor').insertBlock(createBlock(blockName));
31
+ });
32
+
33
+ rootContainer.prepend(button);
34
+ };
35
+
36
+ /**
37
+ * Creates a new top-level block appender. It allows to specify the block name to be
38
+ * created when the appender is clicked and customize the tooltip text displayed on the appender.
39
+ *
40
+ * @param {string} blockName - The name of the block to be created when the appender is clicked.
41
+ * @param {string} [tooltipText='Add Row'] - The tooltip text displayed on the appender.
42
+ */
43
+ export const initRootBlockAppender = (blockName, tooltipText = 'Add Row') => {
44
+ domReady(() => waitForContainer(() => initialize(blockName, tooltipText), ROOT_CONTAINER_SELECTOR));
45
+ };
@@ -0,0 +1,32 @@
1
+ import { addFilter } from '@wordpress/hooks';
2
+ import { initRootBlockAppender } from './initRootBlockAppender.js';
3
+
4
+ /**
5
+ * Sets the root block in the Gutenberg editor and optionally adds the root appender.
6
+ *
7
+ * This function registers a filter to override the inserter support for blocks that are not the specified root block name.
8
+ * It also optionally initializes the root appender with the provided block name and tooltip text, and makes the click
9
+ * on the appender insert the specified block.
10
+ *
11
+ * @param {string} rootBlockName - The name of the block to be set as the root block.
12
+ * @param {boolean} [initAppender=true] - Flag to indicate whether to initialize the root appender.
13
+ * @param {string} [appenderTooltipText='Add Row'] - The tooltip text to be displayed on the root appender.
14
+ */
15
+ export const setRootBlock = (rootBlockName, initAppender = true, appenderTooltipText = 'Add Row') => {
16
+ addFilter(
17
+ 'blocks.registerBlockType',
18
+ 'ssm/with-root-block',
19
+ (settings, name) => {
20
+ // Override the inserter support for blocks that are not the rootBlockName
21
+ if (name !== rootBlockName && !settings.ancestor) {
22
+ settings.ancestor = [rootBlockName];
23
+ }
24
+
25
+ return settings;
26
+ }
27
+ );
28
+
29
+ if (initAppender) {
30
+ initRootBlockAppender(rootBlockName, appenderTooltipText);
31
+ }
32
+ }
@@ -0,0 +1,40 @@
1
+ ## Overview
2
+
3
+ The `waitForContainer` function is a utility that periodically checks for the presence of a specified container element in the DOM. Once the container is found, it calls the provided initialization function. This is particularly useful for initializing scripts that depend on the presence of specific elements that may not be immediately available when the page loads.
4
+
5
+ ## Function Signature
6
+
7
+ ```javascript
8
+ /**
9
+ * Periodically checks for the presence of the container and initializes the script when found.
10
+ *
11
+ * @param {function} initializeFn - The initialization function to call when the root container is found.
12
+ * @param {string} [containerClass='.is-root-container'] - The CSS class of the container to check for. Default is '.is-root-container'.
13
+ * @param {number} [maxAttempts=50] - The maximum number of attempts to check for the root container.
14
+ * @param {number} [interval=100] - The interval between each check in milliseconds.
15
+ */
16
+ export const waitForContainer = (initializeFn, containerClass = '.is-root-container', maxAttempts = 50, interval = 100);
17
+ ```
18
+
19
+ ### Parameters
20
+
21
+ - **initializeFn** (`function`): The initialization function to call when the container is found.
22
+ - **containerClass** (`string`, optional): The CSS class of the container to check for. Default is `.is-root-container`.
23
+ - **maxAttempts** (`number`, optional): The maximum number of attempts to check for the root container. Default is `50`.
24
+ - **interval** (`number`, optional): The interval between each check in milliseconds. Default is `100`.
25
+
26
+ ## Usage example
27
+
28
+ To use the `waitForContainer` function, import it into your script and pass the necessary parameters:
29
+
30
+ ```javascript
31
+ import { waitForContainer } from '@secretstache/wordpress-gutenberg';
32
+
33
+ const initialize = () => {
34
+ // Your initialization code here
35
+ };
36
+
37
+ waitForContainer(initialize, '.custom-container-class');
38
+ ```
39
+
40
+ In this example, the function will periodically check for the presence of an element with the class `.custom-container-class`. Once found, it will call the `initialize` function.
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Periodically checks for the presence of the container and initializes the script when found.
3
+ *
4
+ * @param {function} initializeFn - The initialization function to call when the container is found.
5
+ * This function receives the root container element as an argument.
6
+ * @param {string} [containerClass='.is-root-container'] - The CSS class of the container to check for. Default is '.is-root-container'.
7
+ * @param {number} [maxAttempts=50] - The maximum number of attempts to check for the root container.
8
+ * @param {number} [interval=100] - The interval between each check in milliseconds.
9
+ */
10
+ export const waitForContainer = (initializeFn, containerClass = '.is-root-container', maxAttempts = 50, interval = 100) => {
11
+ let attempts = 0;
12
+ const checkInterval = setInterval(() => {
13
+ const rootContainer = document.querySelector(containerClass);
14
+ if (rootContainer) {
15
+ clearInterval(checkInterval);
16
+ initializeFn(rootContainer);
17
+ } else {
18
+ attempts += 1;
19
+ if (attempts >= maxAttempts) {
20
+ clearInterval(checkInterval);
21
+ console.error('Max attempts reached. Root container not found.');
22
+ }
23
+ }
24
+ }, interval);
25
+ };