@secretstache/wordpress-gutenberg 0.4.13 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
package/build/styles.css CHANGED
@@ -20,50 +20,6 @@
20
20
  .editor-styles-wrapper .block-editor-block-list__layout.is-root-container {
21
21
  margin-bottom: 0;
22
22
  padding-bottom: 0; }
23
- .editor-styles-wrapper .block-editor-block-list__layout.is-root-container .bc-image-wrapper {
24
- position: relative;
25
- height: auto;
26
- align-self: start; }
27
- .editor-styles-wrapper .block-editor-block-list__layout.is-root-container .bc-image-wrapper__actions {
28
- display: none;
29
- position: absolute;
30
- top: 0;
31
- left: 0;
32
- right: 0;
33
- bottom: 0;
34
- flex-wrap: nowrap;
35
- justify-content: center;
36
- align-items: center;
37
- gap: 20px;
38
- padding: 5px 15px;
39
- z-index: 20; }
40
- @media screen and (max-width: 768px) {
41
- .editor-styles-wrapper .block-editor-block-list__layout.is-root-container .bc-image-wrapper__actions {
42
- gap: 15px; } }
43
- .editor-styles-wrapper .block-editor-block-list__layout.is-root-container .bc-image-wrapper__btn {
44
- font-size: 14px !important;
45
- backdrop-filter: blur(16px) saturate(180%);
46
- background: rgba(255, 255, 255, 0.75);
47
- flex-grow: 1;
48
- justify-content: center;
49
- max-width: 130px; }
50
- @media screen and (max-width: 768px) {
51
- .editor-styles-wrapper .block-editor-block-list__layout.is-root-container .bc-image-wrapper__btn {
52
- padding: 5px; } }
53
- .editor-styles-wrapper .block-editor-block-list__layout.is-root-container .bc-image-wrapper__overlay {
54
- display: none;
55
- position: absolute;
56
- top: 0;
57
- left: 0;
58
- right: 0;
59
- bottom: 0;
60
- z-index: 10;
61
- background: rgba(255, 255, 255, 0.3);
62
- backdrop-filter: blur(3px); }
63
- .editor-styles-wrapper .block-editor-block-list__layout.is-root-container .bc-image-wrapper:hover .bc-image-wrapper__actions {
64
- display: flex; }
65
- .editor-styles-wrapper .block-editor-block-list__layout.is-root-container .bc-image-wrapper:hover .bc-image-wrapper__overlay {
66
- display: block; }
67
23
  .editor-styles-wrapper .block-editor-block-list__layout.is-root-container .root-block-appender {
68
24
  position: absolute;
69
25
  bottom: 30px;
@@ -271,6 +227,59 @@
271
227
  .editor-sidebar .bc-spacing-range-control .components-base-control__help {
272
228
  margin-left: -11px; }
273
229
 
230
+ .editor-styles-wrapper .block-editor-block-list__layout.is-root-container .bc-image-wrapper,
231
+ .editor-sidebar .bc-image-wrapper {
232
+ position: relative;
233
+ height: auto;
234
+ align-self: start; }
235
+ .editor-styles-wrapper .block-editor-block-list__layout.is-root-container .bc-image-wrapper__actions,
236
+ .editor-sidebar .bc-image-wrapper__actions {
237
+ display: none;
238
+ position: absolute;
239
+ top: 0;
240
+ left: 0;
241
+ right: 0;
242
+ bottom: 0;
243
+ flex-wrap: nowrap;
244
+ justify-content: center;
245
+ align-items: center;
246
+ gap: 20px;
247
+ padding: 5px 15px;
248
+ z-index: 20; }
249
+ @media screen and (max-width: 768px) {
250
+ .editor-styles-wrapper .block-editor-block-list__layout.is-root-container .bc-image-wrapper__actions,
251
+ .editor-sidebar .bc-image-wrapper__actions {
252
+ gap: 15px; } }
253
+ .editor-styles-wrapper .block-editor-block-list__layout.is-root-container .bc-image-wrapper__btn,
254
+ .editor-sidebar .bc-image-wrapper__btn {
255
+ font-size: 14px !important;
256
+ backdrop-filter: blur(16px) saturate(180%);
257
+ background: rgba(255, 255, 255, 0.75);
258
+ flex-grow: 1;
259
+ justify-content: center;
260
+ max-width: 130px; }
261
+ @media screen and (max-width: 768px) {
262
+ .editor-styles-wrapper .block-editor-block-list__layout.is-root-container .bc-image-wrapper__btn,
263
+ .editor-sidebar .bc-image-wrapper__btn {
264
+ padding: 5px; } }
265
+ .editor-styles-wrapper .block-editor-block-list__layout.is-root-container .bc-image-wrapper__overlay,
266
+ .editor-sidebar .bc-image-wrapper__overlay {
267
+ display: none;
268
+ position: absolute;
269
+ top: 0;
270
+ left: 0;
271
+ right: 0;
272
+ bottom: 0;
273
+ z-index: 10;
274
+ background: rgba(255, 255, 255, 0.3);
275
+ backdrop-filter: blur(3px); }
276
+ .editor-styles-wrapper .block-editor-block-list__layout.is-root-container .bc-image-wrapper:hover .bc-image-wrapper__actions,
277
+ .editor-sidebar .bc-image-wrapper:hover .bc-image-wrapper__actions {
278
+ display: flex; }
279
+ .editor-styles-wrapper .block-editor-block-list__layout.is-root-container .bc-image-wrapper:hover .bc-image-wrapper__overlay,
280
+ .editor-sidebar .bc-image-wrapper:hover .bc-image-wrapper__overlay {
281
+ display: block; }
282
+
274
283
  .block-editor-block-types-list > [role=presentation] {
275
284
  justify-content: center; }
276
285
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@secretstache/wordpress-gutenberg",
3
- "version": "0.4.13",
3
+ "version": "0.5.0",
4
4
  "description": "",
5
5
  "author": "Secret Stache",
6
6
  "license": "GPL-2.0-or-later",
@@ -27,7 +27,6 @@ const Control = ({ label, max, min, value, onChange, disabled, tooltip, ...other
27
27
  max={max}
28
28
  marks={generateMarks(min, max)}
29
29
  resetFallbackValue={-1}
30
- help="Use -1 for default settings."
31
30
  renderTooltipContent={(value) => {
32
31
  if (value === -1) return 'Default';
33
32
 
@@ -20,6 +20,6 @@ export const useAllowedBlocks = (blockName, excludedBlocks) => {
20
20
  && (!blockHasAncestor || isAncestor);
21
21
  })
22
22
  ?.map((block) => block.name),
23
- [allBlocks, excludedBlocks, blockName],
23
+ [ allBlocks, excludedBlocks, blockName ],
24
24
  );
25
25
  };
@@ -33,7 +33,6 @@
33
33
  }
34
34
 
35
35
  .editor-styles-wrapper .block-editor-block-list__layout.is-root-container {
36
- @import "image-wrapper";
37
36
  @import "root-block-appender";
38
37
  @import "empty-block-appender";
39
38
  @import "new-child-btn";
@@ -74,6 +73,11 @@
74
73
  @import "responsive-spacing";
75
74
  }
76
75
 
76
+ .editor-styles-wrapper .block-editor-block-list__layout.is-root-container,
77
+ .editor-sidebar {
78
+ @import "image-wrapper";
79
+ }
80
+
77
81
  .block-editor-block-types-list > [role=presentation] {
78
82
  justify-content: center;
79
83
  }
@@ -1,3 +1,4 @@
1
+ import { filters } from '@wordpress/hooks';
1
2
  import apiFetch from '@wordpress/api-fetch';
2
3
  import slugify from 'slugify';
3
4
  import classNames from 'classnames';
@@ -144,3 +145,23 @@ export const getSpacingClasses = (
144
145
  [`${mobilePrefix}pb-${spacing?.mobile?.padding?.bottom}`]: spacing?.mobile?.padding?.bottom !== -1,
145
146
  });
146
147
  };
148
+
149
+ /**
150
+ * @param namespace
151
+ * @returns {*[]}
152
+ */
153
+ const getFiltersByNamespace = (namespace) => {
154
+ const list = [];
155
+
156
+ Object.entries(filters).forEach(([filterName, filterData]) => {
157
+ const handlers = filterData.handlers || [];
158
+
159
+ handlers.forEach((handler) => {
160
+ if (handler.namespace.startsWith(namespace)) {
161
+ list.push({ filterName, namespace: handler.namespace });
162
+ }
163
+ });
164
+ });
165
+
166
+ return list;
167
+ };
@@ -1,5 +1,5 @@
1
1
  export * from './rootBlock/index.js';
2
- export * from './waitForContainer/index.js';
2
+ export * from './rootContainer/index.js';
3
3
 
4
4
  export * from './attributes.js';
5
5
  export * from './constants.js';
@@ -1,25 +1,17 @@
1
1
  import { createBlock } from '@wordpress/blocks';
2
2
  import { dispatch } from '@wordpress/data';
3
3
 
4
- import { waitForContainer } from '../waitForContainer/index.js';
4
+ import { getRootContainer } from '../rootContainer/index.js';
5
5
 
6
- const ROOT_CONTAINER_SELECTOR = '.is-root-container';
7
- const ROOT_BLOCK_APPENDER_SELECTOR = '.is-root-container .root-block-appender';
6
+ const ROOT_BLOCK_APPENDER_SELECTOR = '.root-block-appender';
8
7
 
9
8
  /**
10
9
  * Initializes the custom button for the root appender.
10
+ * @param {Element} rootContainer - The root container of the editor.
11
11
  * @param {string} blockName - The name of the block to be created when the appender is clicked.
12
12
  * @param {string} tooltipText - The tooltip text displayed on the appender.
13
13
  */
14
- const initialize = (blockName, tooltipText) => {
15
- const rootContainer = document.querySelector(ROOT_CONTAINER_SELECTOR);
16
-
17
- if (!rootContainer) {
18
- console.error('Root container not found');
19
-
20
- return;
21
- }
22
-
14
+ const initialize = (rootContainer, blockName, tooltipText) => {
23
15
  const button = document.createElement('button');
24
16
 
25
17
  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>';
@@ -32,6 +24,8 @@ const initialize = (blockName, tooltipText) => {
32
24
  });
33
25
 
34
26
  rootContainer.prepend(button);
27
+
28
+ return !!rootContainer.querySelector(ROOT_BLOCK_APPENDER_SELECTOR);
35
29
  };
36
30
 
37
31
  /**
@@ -42,13 +36,27 @@ const initialize = (blockName, tooltipText) => {
42
36
  * @param {string} [tooltipText='Add Row'] - The tooltip text displayed on the appender.
43
37
  */
44
38
  export const setRootBlockAppender = (blockName, tooltipText = 'Add Row') => {
45
- waitForContainer(() => initialize(blockName, tooltipText), ROOT_CONTAINER_SELECTOR);
39
+ const rootContainer = getRootContainer();
40
+
41
+ if (rootContainer) {
42
+ initialize(rootContainer, blockName, tooltipText);
43
+ } else {
44
+ console.error('Root container is not found.')
45
+ }
46
46
  };
47
47
 
48
48
  export const unsetRootBlockAppender = () => {
49
- const appender = document.querySelector(ROOT_BLOCK_APPENDER_SELECTOR);
50
-
51
- if (appender) {
52
- appender.remove();
49
+ const rootContainer = getRootContainer();
50
+
51
+ if (rootContainer) {
52
+ const appender = rootContainer.querySelector(ROOT_BLOCK_APPENDER_SELECTOR);
53
+
54
+ if (appender) {
55
+ appender.remove();
56
+ } else {
57
+ console.error('Root block appender is not found.');
58
+ }
59
+ } else {
60
+ console.error('Root container is not found.')
53
61
  }
54
- }
62
+ };
@@ -1,4 +1,6 @@
1
- export * from './setRootBlock.js';
2
- export * from './setRootBlockAppender.js';
1
+ export * from './setRootBlockForPostTypes.js'
2
+ export * from './setRootBlockFilter.js'
3
+ export * from './unsetRootBlockFilter.js';
4
+ export * from './rootBlockVisibilityFilter.js';
5
+ export * from './appender.js';
3
6
  export * from './hideRootBlockForInlineInserter.js';
4
- export * from './hideRootBlockForOtherBlocks.js';
@@ -0,0 +1,35 @@
1
+ import { addFilter, removeFilter } from '@wordpress/hooks';
2
+ import { getBlockTypes } from '@wordpress/blocks';
3
+
4
+ export const rootBlockVisibilityFilter = {
5
+ add({ rootBlockName }) {
6
+ addFilter(
7
+ 'blocks.registerBlockType',
8
+ 'ssm/root-block-visibility',
9
+ (blockSettings, blockName) => {
10
+ const isRootBlock = blockName === rootBlockName;
11
+ const hasOwnAllowedBlocks = !!blockSettings?.allowedBlocks;
12
+ const hasParent = !!blockSettings?.parent;
13
+
14
+ if (isRootBlock || hasParent || hasOwnAllowedBlocks) {
15
+ return blockSettings;
16
+ }
17
+
18
+ // get all blockTypes
19
+ blockSettings.allowedBlocks = getBlockTypes()
20
+ ?.filter((allowedBlock) => {
21
+ const isRootBlock = allowedBlock.name === rootBlockName;
22
+ const hasParent = !!allowedBlock?.parent;
23
+
24
+ return !isRootBlock && !hasParent;
25
+ })
26
+ ?.map(allowedBlock => allowedBlock.name);
27
+
28
+ return blockSettings;
29
+ },
30
+ );
31
+ },
32
+ remove() {
33
+ removeFilter('blocks.registerBlockType', 'ssm/root-block-visibility');
34
+ },
35
+ };
@@ -0,0 +1,29 @@
1
+ import { addFilter, removeFilter } from '@wordpress/hooks';
2
+
3
+ /**
4
+ * Adds a filter to set the specified block as the root block by modifying block settings during registration.
5
+ * Blocks other than the root block will have their 'ancestor' property set to the root block name,
6
+ * making them only insertable within the root block.
7
+ */
8
+ export const setRootBlockFilter = {
9
+ add(rootBlockName) {
10
+ addFilter(
11
+ 'blocks.registerBlockType',
12
+ 'ssm/set-root-block',
13
+ (settings, name) => {
14
+ const isRootBlock = name === rootBlockName;
15
+ const isBaseBlock = name === 'core/block';
16
+ const hasAncestor = !!settings?.ancestor;
17
+
18
+ if (!isRootBlock && !isBaseBlock && !hasAncestor) {
19
+ settings.ancestor = [rootBlockName];
20
+ }
21
+
22
+ return settings;
23
+ },
24
+ );
25
+ },
26
+ remove() {
27
+ removeFilter('blocks.registerBlockType', 'ssm/set-root-block');
28
+ },
29
+ };
@@ -0,0 +1,73 @@
1
+ import { dispatch, select, subscribe } from '@wordpress/data';
2
+ import { setRootBlockFilter } from './setRootBlockFilter.js';
3
+ import { unsetRootBlockFilter } from './unsetRootBlockFilter.js';
4
+ import { rootBlockVisibilityFilter } from './rootBlockVisibilityFilter.js';
5
+ import { waitForRootContainer } from '../rootContainer/index.js';
6
+ import { setRootBlockAppender, unsetRootBlockAppender } from './appender.js';
7
+
8
+ /**
9
+ * Configures a root block for specific post types
10
+ *
11
+ * @param {string} rootBlockName - The name of the root block to set.
12
+ * @param {Array<string>} [postTypes=['page', 'post']] - The post types for which the root block should be enabled.
13
+ * @param {Function} [callback] - Optional callback to execute when the root block state changes.
14
+ * @param {Array<Object>} [filters=[rootBlockVisibilityFilter]] - Filters to apply or remove when enabling/disabling the root block.
15
+ * @param {boolean} [initAppender=true] - Whether to initialize the root block appender.
16
+ * @param {string} [appenderTooltipText='Add Row'] - Tooltip text for the root block appender.
17
+ */
18
+ export const setRootBlockForPostTypes = (
19
+ rootBlockName,
20
+ postTypes = ['page', 'post'],
21
+ callback,
22
+ filters = [ rootBlockVisibilityFilter ],
23
+ initAppender = true,
24
+ appenderTooltipText = 'Add Row',
25
+ ) => {
26
+ let isRootBlockEnabled = false;
27
+
28
+ waitForRootContainer().then(() => {
29
+ console.log('Root Container found.');
30
+
31
+ subscribe(() => {
32
+ const currentPostType = select('core/editor').getCurrentPostType();
33
+
34
+ if (postTypes.includes(currentPostType) && !isRootBlockEnabled) {
35
+ isRootBlockEnabled = true;
36
+
37
+ setRootBlockFilter.add(rootBlockName);
38
+ unsetRootBlockFilter.remove();
39
+
40
+ if (filters?.length > 0) {
41
+ filters.forEach((filter) => filter.add({ rootBlockName, isRootBlockEnabled, currentPostType }));
42
+ dispatch('core/blocks').reapplyBlockTypeFilters();
43
+ }
44
+
45
+ if (callback) {
46
+ callback({ isRootBlockEnabled, currentPostType });
47
+ }
48
+
49
+ if (initAppender) {
50
+ setRootBlockAppender(rootBlockName, appenderTooltipText);
51
+ }
52
+ } else if (!postTypes.includes(currentPostType) && isRootBlockEnabled) {
53
+ isRootBlockEnabled = false;
54
+
55
+ setRootBlockFilter.remove()
56
+ unsetRootBlockFilter.add(rootBlockName);
57
+
58
+ if (filters?.length > 0) {
59
+ filters.forEach((filter) => filter.remove({ rootBlockName, isRootBlockEnabled, currentPostType }));
60
+ dispatch('core/blocks').reapplyBlockTypeFilters();
61
+ }
62
+
63
+ if (callback) {
64
+ callback({ isRootBlockEnabled, currentPostType });
65
+ }
66
+
67
+ if (initAppender) {
68
+ unsetRootBlockAppender();
69
+ }
70
+ }
71
+ }, 'core/block-editor');
72
+ })
73
+ };
@@ -0,0 +1,26 @@
1
+ import { addFilter, removeFilter } from '@wordpress/hooks';
2
+
3
+ /**
4
+ * Adds a filter to unset the root block restrictions by removing the 'ancestor' property from block settings
5
+ * if it includes the specified root block name.
6
+ */
7
+ export const unsetRootBlockFilter = {
8
+ add(rootBlockName) {
9
+ addFilter(
10
+ 'blocks.registerBlockType',
11
+ 'ssm/unset-root-block',
12
+ (settings) => {
13
+ const hasRootAncestor = settings.ancestor && settings.ancestor.includes(rootBlockName);
14
+
15
+ if (hasRootAncestor) {
16
+ settings.ancestor = null;
17
+ }
18
+
19
+ return settings;
20
+ },
21
+ );
22
+ },
23
+ remove() {
24
+ removeFilter('blocks.registerBlockType', 'ssm/unset-root-block');
25
+ },
26
+ };
@@ -0,0 +1,72 @@
1
+ ## Overview
2
+
3
+ The `waitForRootContainer` function is a utility that periodically checks for the presence of the Gutenberg editor's root container, identified by the class `.is-root-container`. Once the container is found, it resolves a promise, allowing for additional initialization logic.
4
+
5
+ ## Function Signature
6
+
7
+ ```javascript
8
+ /**
9
+ * Periodically checks for the presence of the Gutenberg editor's root container and resolves when found.
10
+ *
11
+ * @param {number} [maxAttempts=10] - The maximum number of attempts to check for the root container.
12
+ * @param {number} [interval=500] - The interval time (in milliseconds) between attempts.
13
+ * @returns {Promise<Element>} - Resolves with the root container element if found, or rejects if not found after max attempts.
14
+ */
15
+ export const waitForRootContainer = (maxAttempts = 10, interval = 500);
16
+ ```
17
+
18
+ ### Parameters
19
+ - **maxAttempts** (`number`, optional): The maximum number of attempts to check for the root container. Default is `10`.
20
+ - **interval** (`number`, optional): The interval time (in milliseconds) between each attempt. Default is `500`.
21
+
22
+ ### Returns
23
+ - **Promise<Element>**: Resolves with the root container element when found. Rejects with an error if the container is not found after the maximum attempts.
24
+
25
+ ---
26
+
27
+ ## Usage Example
28
+ To use the `waitForRootContainer` function, import it into your script and handle the promise:
29
+
30
+ ```javascript
31
+ import { waitForRootContainer } from '@secretstache/wordpress-gutenberg';
32
+
33
+ waitForRootContainer(10, 500)
34
+ .then((rootContainer) => {
35
+ console.log('Gutenberg root container found:', rootContainer);
36
+ // Your initialization logic here
37
+ })
38
+ .catch((error) => {
39
+ console.error('Failed to find Gutenberg root container:', error);
40
+ });
41
+ ```
42
+
43
+ ### Example Output
44
+ In this example:
45
+ - The function checks for the Gutenberg root container (identified by `.is-root-container`) up to 10 times, waiting 500ms between each check.
46
+ - If the container is found, it resolves with the container element.
47
+ - If the container is not found after 10 attempts, it rejects with an error.
48
+
49
+ ---
50
+
51
+ ## getRootContainer
52
+
53
+ The `getRootContainer` function retrieves the Gutenberg editor's root container element from the DOM.
54
+
55
+ ### Usage Example
56
+
57
+ ```javascript
58
+ import { getRootContainer } from '@secretstache/wordpress-gutenberg';
59
+
60
+ const rootContainer = getRootContainer();
61
+ if (rootContainer) {
62
+ console.log('Gutenberg root container found:', rootContainer);
63
+ } else {
64
+ console.log('Gutenberg root container not found');
65
+ }
66
+ ```
67
+
68
+ ---
69
+
70
+ ### Notes
71
+ - The `getRootContainer` function searches both the main DOM and an iframe (if applicable) for the Gutenberg root container with the class `.is-root-container`.
72
+ - `waitForRootContainer` is built on top of `getRootContainer` and provides retry logic for situations where the root container is not immediately available.
@@ -0,0 +1,42 @@
1
+ const ROOT_CONTAINER_SELECTOR = '.is-root-container';
2
+
3
+ /**
4
+ * Retrieves the Gutenberg editor's root container element from the DOM or an iframe.
5
+ *
6
+ * @returns {Element|null} - Returns the root container element if found, or null if not found.
7
+ */
8
+ export const getRootContainer = () => {
9
+ const rootContainer = document.querySelector(ROOT_CONTAINER_SELECTOR);
10
+
11
+ if (rootContainer) {
12
+ return rootContainer;
13
+ }
14
+
15
+ const iframe = document.querySelector('.block-editor iframe');
16
+ const iframeDocument = iframe?.contentDocument || iframe?.contentWindow?.document;
17
+
18
+ return iframeDocument?.querySelector(ROOT_CONTAINER_SELECTOR) || null;
19
+ };
20
+
21
+ export const waitForRootContainer = (maxAttempts = 10, interval = 500) => {
22
+ return new Promise((resolve, reject) => {
23
+ let attempts = 0;
24
+
25
+ const checkRootContainer = () => {
26
+ const rootContainer = getRootContainer();
27
+
28
+ if (rootContainer) {
29
+ return resolve(rootContainer);
30
+ } else {
31
+ if (attempts <= maxAttempts) {
32
+ attempts++;
33
+ setTimeout(checkRootContainer, interval);
34
+ } else {
35
+ reject(new Error('Root container not found after max attempts.'));
36
+ }
37
+ }
38
+ };
39
+
40
+ checkRootContainer();
41
+ });
42
+ };