@wordpress/block-editor 15.14.0 → 15.14.1-next.v.202603102151.0
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/components/block-tools/index.cjs +4 -3
- package/build/components/block-tools/index.cjs.map +2 -2
- package/build/components/iframe/index.cjs +14 -6
- package/build/components/iframe/index.cjs.map +2 -2
- package/build/components/observe-typing/index.cjs +9 -13
- package/build/components/observe-typing/index.cjs.map +2 -2
- package/build/components/provider/index.cjs +22 -6
- package/build/components/provider/index.cjs.map +2 -2
- package/build/hooks/block-fields/index.cjs +52 -31
- package/build/hooks/block-fields/index.cjs.map +2 -2
- package/build/hooks/cross-origin-isolation.cjs +7 -73
- package/build/hooks/cross-origin-isolation.cjs.map +2 -2
- package/build/private-apis.cjs +1 -0
- package/build/private-apis.cjs.map +2 -2
- package/build/store/private-keys.cjs +3 -0
- package/build/store/private-keys.cjs.map +2 -2
- package/build/store/selectors.cjs +9 -7
- package/build/store/selectors.cjs.map +2 -2
- package/build-module/components/block-tools/index.mjs +4 -3
- package/build-module/components/block-tools/index.mjs.map +2 -2
- package/build-module/components/iframe/index.mjs +14 -6
- package/build-module/components/iframe/index.mjs.map +2 -2
- package/build-module/components/observe-typing/index.mjs +9 -13
- package/build-module/components/observe-typing/index.mjs.map +2 -2
- package/build-module/components/provider/index.mjs +22 -6
- package/build-module/components/provider/index.mjs.map +2 -2
- package/build-module/hooks/block-fields/index.mjs +53 -32
- package/build-module/hooks/block-fields/index.mjs.map +2 -2
- package/build-module/hooks/cross-origin-isolation.mjs +7 -73
- package/build-module/hooks/cross-origin-isolation.mjs.map +2 -2
- package/build-module/private-apis.mjs +3 -1
- package/build-module/private-apis.mjs.map +2 -2
- package/build-module/store/private-keys.mjs +2 -0
- package/build-module/store/private-keys.mjs.map +2 -2
- package/build-module/store/selectors.mjs +9 -7
- package/build-module/store/selectors.mjs.map +2 -2
- package/build-style/style-rtl.css +8 -5
- package/build-style/style.css +8 -5
- package/package.json +39 -39
- package/src/components/block-tools/index.js +11 -4
- package/src/components/iframe/index.js +19 -6
- package/src/components/observe-typing/index.js +10 -14
- package/src/components/provider/index.js +47 -5
- package/src/components/responsive-block-control/style.scss +1 -0
- package/src/hooks/block-fields/index.js +44 -19
- package/src/hooks/block-fields/styles.scss +7 -9
- package/src/hooks/cross-origin-isolation.js +8 -107
- package/src/hooks/test/cross-origin-isolation.js +11 -42
- package/src/private-apis.js +2 -0
- package/src/store/private-keys.js +1 -0
- package/src/store/selectors.js +27 -9
- package/src/store/test/selectors.js +540 -0
|
@@ -1,34 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*/
|
|
4
|
-
import { addFilter } from '@wordpress/hooks';
|
|
5
|
-
import { createHigherOrderComponent } from '@wordpress/compose';
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Adds crossorigin and credentialless attributes to elements as needed.
|
|
2
|
+
* Adds crossorigin="anonymous" to an element if missing.
|
|
9
3
|
*
|
|
10
4
|
* @param {Element} el The element to modify.
|
|
11
5
|
*/
|
|
12
|
-
function
|
|
13
|
-
// Add the crossorigin attribute if missing.
|
|
6
|
+
function addCrossOriginAttribute( el ) {
|
|
14
7
|
if ( ! el.hasAttribute( 'crossorigin' ) ) {
|
|
15
8
|
el.setAttribute( 'crossorigin', 'anonymous' );
|
|
16
9
|
}
|
|
17
|
-
|
|
18
|
-
// For iframes, add the credentialless attribute.
|
|
19
|
-
if ( el.nodeName === 'IFRAME' && ! el.hasAttribute( 'credentialless' ) ) {
|
|
20
|
-
// Do not modify the iframed editor canvas.
|
|
21
|
-
if ( el.getAttribute( 'src' )?.startsWith( 'blob:' ) ) {
|
|
22
|
-
return;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
el.setAttribute( 'credentialless', '' );
|
|
26
|
-
|
|
27
|
-
// Reload the iframe to ensure the new attribute is taken into account.
|
|
28
|
-
const origSrc = el.getAttribute( 'src' ) || '';
|
|
29
|
-
el.setAttribute( 'src', '' );
|
|
30
|
-
el.setAttribute( 'src', origSrc );
|
|
31
|
-
}
|
|
32
10
|
}
|
|
33
11
|
|
|
34
12
|
// Only add the mutation observer if the site is cross-origin isolated.
|
|
@@ -50,58 +28,17 @@ if ( window.crossOriginIsolated ) {
|
|
|
50
28
|
}
|
|
51
29
|
|
|
52
30
|
el.querySelectorAll(
|
|
53
|
-
'img,source,script,video,link
|
|
31
|
+
'img,source,script,video,link'
|
|
54
32
|
).forEach( ( v ) => {
|
|
55
|
-
|
|
33
|
+
addCrossOriginAttribute( v );
|
|
56
34
|
} );
|
|
57
35
|
|
|
58
|
-
if ( el.nodeName === 'IFRAME' ) {
|
|
59
|
-
const iframeNode = el;
|
|
60
|
-
|
|
61
|
-
/*
|
|
62
|
-
* Sandboxed iframes should not get modified. For example embedding a tweet served in a sandboxed
|
|
63
|
-
* iframe, the tweet itself would not be modified.
|
|
64
|
-
*/
|
|
65
|
-
const isEmbedSandboxIframe =
|
|
66
|
-
iframeNode.classList.contains(
|
|
67
|
-
'components-sandbox'
|
|
68
|
-
);
|
|
69
|
-
|
|
70
|
-
if ( ! isEmbedSandboxIframe ) {
|
|
71
|
-
iframeNode.addEventListener( 'load', () => {
|
|
72
|
-
try {
|
|
73
|
-
if (
|
|
74
|
-
iframeNode.contentDocument &&
|
|
75
|
-
iframeNode.contentDocument.body
|
|
76
|
-
) {
|
|
77
|
-
observer.observe(
|
|
78
|
-
iframeNode.contentDocument,
|
|
79
|
-
{
|
|
80
|
-
childList: true,
|
|
81
|
-
attributes: true,
|
|
82
|
-
subtree: true,
|
|
83
|
-
}
|
|
84
|
-
);
|
|
85
|
-
}
|
|
86
|
-
} catch ( e ) {
|
|
87
|
-
// Iframe may be cross-origin or otherwise inaccessible.
|
|
88
|
-
// Silently ignore these cases.
|
|
89
|
-
}
|
|
90
|
-
} );
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
36
|
if (
|
|
95
|
-
[
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
'SCRIPT',
|
|
99
|
-
'VIDEO',
|
|
100
|
-
'LINK',
|
|
101
|
-
'IFRAME',
|
|
102
|
-
].includes( el.nodeName )
|
|
37
|
+
[ 'IMG', 'SOURCE', 'SCRIPT', 'VIDEO', 'LINK' ].includes(
|
|
38
|
+
el.nodeName
|
|
39
|
+
)
|
|
103
40
|
) {
|
|
104
|
-
|
|
41
|
+
addCrossOriginAttribute( el );
|
|
105
42
|
}
|
|
106
43
|
} );
|
|
107
44
|
} );
|
|
@@ -134,39 +71,3 @@ if ( window.crossOriginIsolated ) {
|
|
|
134
71
|
|
|
135
72
|
startObservingBody();
|
|
136
73
|
}
|
|
137
|
-
|
|
138
|
-
// Only apply the embed preview filter when cross-origin isolated.
|
|
139
|
-
if ( window.crossOriginIsolated ) {
|
|
140
|
-
const supportsCredentialless =
|
|
141
|
-
'credentialless' in window.HTMLIFrameElement.prototype;
|
|
142
|
-
|
|
143
|
-
const disableEmbedPreviews = createHigherOrderComponent(
|
|
144
|
-
( BlockEdit ) =>
|
|
145
|
-
function DisableEmbedPreviews( props ) {
|
|
146
|
-
if ( 'core/embed' !== props.name ) {
|
|
147
|
-
return <BlockEdit { ...props } />;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// List of embeds that do not support a preview is from packages/block-library/src/embed/variations.js.
|
|
151
|
-
const previewable =
|
|
152
|
-
supportsCredentialless &&
|
|
153
|
-
! [ 'facebook', 'smugmug' ].includes(
|
|
154
|
-
props.attributes.providerNameSlug
|
|
155
|
-
);
|
|
156
|
-
|
|
157
|
-
return (
|
|
158
|
-
<BlockEdit
|
|
159
|
-
{ ...props }
|
|
160
|
-
attributes={ { ...props.attributes, previewable } }
|
|
161
|
-
/>
|
|
162
|
-
);
|
|
163
|
-
},
|
|
164
|
-
'withDisabledEmbedPreview'
|
|
165
|
-
);
|
|
166
|
-
|
|
167
|
-
addFilter(
|
|
168
|
-
'editor.BlockEdit',
|
|
169
|
-
'media-experiments/disable-embed-previews',
|
|
170
|
-
disableEmbedPreviews
|
|
171
|
-
);
|
|
172
|
-
}
|
|
@@ -152,60 +152,29 @@ describe( 'cross-origin-isolation', () => {
|
|
|
152
152
|
expect( observeSpy ).not.toHaveBeenCalled();
|
|
153
153
|
} );
|
|
154
154
|
|
|
155
|
-
it( 'should
|
|
155
|
+
it( 'should add crossorigin="anonymous" to images', async () => {
|
|
156
156
|
Object.defineProperty( window, 'crossOriginIsolated', {
|
|
157
157
|
value: true,
|
|
158
158
|
writable: true,
|
|
159
159
|
configurable: true,
|
|
160
160
|
} );
|
|
161
161
|
|
|
162
|
-
// Re-import the module
|
|
162
|
+
// Re-import the module to trigger the side effects
|
|
163
163
|
jest.isolateModules( () => {
|
|
164
164
|
require( '../cross-origin-isolation' );
|
|
165
165
|
} );
|
|
166
166
|
|
|
167
|
-
// Create an
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
throw new Error( 'Cross-origin access denied' );
|
|
172
|
-
},
|
|
173
|
-
} );
|
|
174
|
-
|
|
175
|
-
// This should not throw an error
|
|
176
|
-
expect( () => {
|
|
177
|
-
document.body.appendChild( iframe );
|
|
178
|
-
iframe.dispatchEvent( new Event( 'load' ) );
|
|
179
|
-
} ).not.toThrow();
|
|
180
|
-
} );
|
|
181
|
-
|
|
182
|
-
it( 'should register embed preview filter when cross-origin isolated', () => {
|
|
183
|
-
Object.defineProperty( window, 'crossOriginIsolated', {
|
|
184
|
-
value: true,
|
|
185
|
-
writable: true,
|
|
186
|
-
configurable: true,
|
|
187
|
-
} );
|
|
188
|
-
|
|
189
|
-
const hasFilter = jest.spyOn(
|
|
190
|
-
require( '@wordpress/hooks' ),
|
|
191
|
-
'hasFilter'
|
|
192
|
-
);
|
|
167
|
+
// Create an image and add it to the DOM
|
|
168
|
+
const img = document.createElement( 'img' );
|
|
169
|
+
img.setAttribute( 'src', 'https://example.com/image.jpg' );
|
|
170
|
+
document.body.appendChild( img );
|
|
193
171
|
|
|
194
|
-
//
|
|
195
|
-
|
|
196
|
-
require( '../cross-origin-isolation' );
|
|
197
|
-
} );
|
|
172
|
+
// Wait for MutationObserver callback to fire (async microtask).
|
|
173
|
+
await new Promise( ( resolve ) => setTimeout( resolve, 0 ) );
|
|
198
174
|
|
|
199
|
-
// The
|
|
200
|
-
|
|
201
|
-
// but we can verify the module loads without errors
|
|
202
|
-
expect( () => {
|
|
203
|
-
require( '@wordpress/hooks' ).hasFilter(
|
|
204
|
-
'editor.BlockEdit',
|
|
205
|
-
'media-experiments/disable-embed-previews'
|
|
206
|
-
);
|
|
207
|
-
} ).not.toThrow();
|
|
175
|
+
// The image should get the crossorigin attribute
|
|
176
|
+
expect( img ).toHaveAttribute( 'crossorigin', 'anonymous' );
|
|
208
177
|
|
|
209
|
-
|
|
178
|
+
document.body.removeChild( img );
|
|
210
179
|
} );
|
|
211
180
|
} );
|
package/src/private-apis.js
CHANGED
|
@@ -48,6 +48,7 @@ import {
|
|
|
48
48
|
deviceTypeKey,
|
|
49
49
|
isIsolatedEditorKey,
|
|
50
50
|
isNavigationOverlayContextKey,
|
|
51
|
+
mediaUploadOnSuccessKey,
|
|
51
52
|
} from './store/private-keys';
|
|
52
53
|
import { requiresWrapperOnCopy } from './components/writing-flow/utils';
|
|
53
54
|
import { PrivateRichText } from './components/rich-text/';
|
|
@@ -129,6 +130,7 @@ lock( privateApis, {
|
|
|
129
130
|
deviceTypeKey,
|
|
130
131
|
isIsolatedEditorKey,
|
|
131
132
|
isNavigationOverlayContextKey,
|
|
133
|
+
mediaUploadOnSuccessKey,
|
|
132
134
|
useBlockElement,
|
|
133
135
|
useBlockElementRef,
|
|
134
136
|
LinkPicker,
|
package/src/store/selectors.js
CHANGED
|
@@ -1741,7 +1741,12 @@ const canInsertBlockTypeUnmemoized = (
|
|
|
1741
1741
|
}
|
|
1742
1742
|
|
|
1743
1743
|
// In content only mode, check if this container allows insertion.
|
|
1744
|
+
// We need the `isParentSectionBlock` check because section blocks
|
|
1745
|
+
// (synced patterns, contentOnly groups) have a `getBlockEditingMode`
|
|
1746
|
+
// of 'default', not 'contentOnly' — the 'contentOnly' mode is only
|
|
1747
|
+
// set on their *children*.
|
|
1744
1748
|
if (
|
|
1749
|
+
isWithinSection &&
|
|
1745
1750
|
( isParentSectionBlock || blockEditingMode === 'contentOnly' ) &&
|
|
1746
1751
|
! isContainerInsertableToInContentOnlyMode(
|
|
1747
1752
|
state,
|
|
@@ -1749,13 +1754,14 @@ const canInsertBlockTypeUnmemoized = (
|
|
|
1749
1754
|
rootClientId
|
|
1750
1755
|
)
|
|
1751
1756
|
) {
|
|
1757
|
+
const defaultBlockName = getDefaultBlockName();
|
|
1752
1758
|
// Allow inserting the default block anywhere that another default block already exists
|
|
1753
1759
|
// when in contentOnly mode.
|
|
1754
|
-
if ( blockName ===
|
|
1760
|
+
if ( blockName === defaultBlockName ) {
|
|
1755
1761
|
const existingBlocks = getBlockOrder( state, rootClientId );
|
|
1756
1762
|
const hasDefaultBlock = existingBlocks.some(
|
|
1757
1763
|
( clientId ) =>
|
|
1758
|
-
getBlockName( state, clientId ) ===
|
|
1764
|
+
getBlockName( state, clientId ) === defaultBlockName
|
|
1759
1765
|
);
|
|
1760
1766
|
if ( ! hasDefaultBlock ) {
|
|
1761
1767
|
return false;
|
|
@@ -1938,11 +1944,18 @@ export function canRemoveBlock( state, clientId ) {
|
|
|
1938
1944
|
|
|
1939
1945
|
const rootBlockEditingMode = getBlockEditingMode( state, rootClientId );
|
|
1940
1946
|
const blockName = getBlockName( state, clientId );
|
|
1941
|
-
|
|
1947
|
+
const defaultBlockName = getDefaultBlockName();
|
|
1948
|
+
|
|
1949
|
+
// Check if the parent container allows insertion/removal in contentOnly
|
|
1950
|
+
// mode. We need the `isParentSectionBlock` check because section blocks
|
|
1951
|
+
// (synced patterns, contentOnly groups) have a `getBlockEditingMode` of
|
|
1952
|
+
// 'default', not 'contentOnly' — the 'contentOnly' mode is only set on
|
|
1953
|
+
// their *children*.
|
|
1942
1954
|
if (
|
|
1955
|
+
isWithinSection &&
|
|
1943
1956
|
( isParentSectionBlock ||
|
|
1944
|
-
|
|
1945
|
-
|
|
1957
|
+
blockName === defaultBlockName ||
|
|
1958
|
+
rootBlockEditingMode === 'contentOnly' ) &&
|
|
1946
1959
|
! isContainerInsertableToInContentOnlyMode(
|
|
1947
1960
|
state,
|
|
1948
1961
|
getBlockName( state, clientId ),
|
|
@@ -1951,10 +1964,10 @@ export function canRemoveBlock( state, clientId ) {
|
|
|
1951
1964
|
) {
|
|
1952
1965
|
// Allow removing the default block when other default blocks exist
|
|
1953
1966
|
// in contentOnly mode.
|
|
1954
|
-
if ( blockName ===
|
|
1967
|
+
if ( blockName === defaultBlockName ) {
|
|
1955
1968
|
const existingBlocks = getBlockOrder( state, rootClientId );
|
|
1956
1969
|
const defaultBlocks = existingBlocks.filter(
|
|
1957
|
-
( id ) => getBlockName( state, id ) ===
|
|
1970
|
+
( id ) => getBlockName( state, id ) === defaultBlockName
|
|
1958
1971
|
);
|
|
1959
1972
|
// Allow removal if there are other default blocks besides this one
|
|
1960
1973
|
if ( defaultBlocks.length > 1 ) {
|
|
@@ -2016,11 +2029,16 @@ export function canMoveBlock( state, clientId ) {
|
|
|
2016
2029
|
return false;
|
|
2017
2030
|
}
|
|
2018
2031
|
|
|
2019
|
-
// If the
|
|
2020
|
-
//
|
|
2032
|
+
// If the block is within a section and the parent is either a section
|
|
2033
|
+
// block itself or has contentOnly editing mode, check whether the inner
|
|
2034
|
+
// block should be allowed to move. We need the `isParentSectionBlock`
|
|
2035
|
+
// check because section blocks (synced patterns, contentOnly groups)
|
|
2036
|
+
// have a `getBlockEditingMode` of 'default', not 'contentOnly' — the
|
|
2037
|
+
// 'contentOnly' mode is only set on their *children*.
|
|
2021
2038
|
const isParentSectionBlock = !! isSectionBlock( state, rootClientId );
|
|
2022
2039
|
const rootBlockEditingMode = getBlockEditingMode( state, rootClientId );
|
|
2023
2040
|
if (
|
|
2041
|
+
isBlockWithinSection &&
|
|
2024
2042
|
( isParentSectionBlock || rootBlockEditingMode === 'contentOnly' ) &&
|
|
2025
2043
|
! isContainerInsertableToInContentOnlyMode(
|
|
2026
2044
|
state,
|