Skip to content

Commit 125844b

Browse files
authored
Merge pull request #5 from BeAPI/feature/iframe-paste-auto-attr
Autorise le collage d'un code iframe dans l'éditeur
2 parents 65010fa + fdffb96 commit 125844b

File tree

6 files changed

+207
-19
lines changed

6 files changed

+207
-19
lines changed

.wp-env.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,5 @@
1-
{}
1+
{
2+
"plugins": [
3+
"."
4+
]
5+
}

src/blockparty-iframe/block.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,21 @@
2828
"url": {
2929
"type": "string",
3030
"default": ""
31+
},
32+
"iframeAttributes": {
33+
"type": "array",
34+
"default": [],
35+
"items": {
36+
"type": "object",
37+
"properties": {
38+
"key": {
39+
"type": "string"
40+
},
41+
"value": {
42+
"type": "string"
43+
}
44+
}
45+
}
3146
}
3247
},
3348
"textdomain": "blockparty-iframe",

src/blockparty-iframe/edit.js

Lines changed: 42 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import { useState } from '@wordpress/element';
3939
import './editor.scss';
4040

4141
import { aspectRatio } from '@wordpress/icons';
42+
import { convertAttributesToProps, parseIframeCode } from './utils';
4243

4344
/**
4445
* The edit function describes the structure of your block in the context of the
@@ -50,12 +51,18 @@ import { aspectRatio } from '@wordpress/icons';
5051
*/
5152
export default function Edit( { attributes, setAttributes } ) {
5253
const blockProps = useBlockProps();
53-
const { lazyload, title: initialTitle, url: initialUrl } = attributes;
54+
const {
55+
lazyload,
56+
title: initialTitle,
57+
url: initialUrl,
58+
iframeAttributes: initialAttributes,
59+
} = attributes;
5460

5561
// State local pour les champs TextControl
5662
const [ iframeData, setIframeData ] = useState( {
5763
url: initialUrl || '',
5864
title: initialTitle || '',
65+
iframeAttributes: initialAttributes || [],
5966
} );
6067

6168
// hasConfirmed = l’utilisateur a validé l’ajout de l’iframe
@@ -76,6 +83,29 @@ export default function Edit( { attributes, setAttributes } ) {
7683
const showPlaceholder = ! hasConfirmed;
7784
const showIframe = hasConfirmed && isIframeElligible;
7885

86+
// Handle URL/iframe code change
87+
function handleUrlChange( value ) {
88+
// Try to parse as iframe code
89+
const parsed = parseIframeCode( value );
90+
91+
if ( parsed ) {
92+
// It's an iframe code, extract URL, title, and attributes
93+
setIframeData( {
94+
...iframeData,
95+
url: parsed.url,
96+
title: parsed.title || iframeData.title, // Use extracted title if available, otherwise keep current
97+
iframeAttributes: parsed.attributes,
98+
} );
99+
} else {
100+
// It's a regular URL
101+
setIframeData( {
102+
...iframeData,
103+
url: value,
104+
iframeAttributes: [],
105+
} );
106+
}
107+
}
108+
79109
// Handle clic Add iframe
80110
function handleAddIframeButtonClick() {
81111
setAttributes( { ...attributes, ...iframeData } );
@@ -118,28 +148,20 @@ export default function Edit( { attributes, setAttributes } ) {
118148
icon={ aspectRatio }
119149
label={ __( 'Iframe', 'blockparty-iframe' ) }
120150
instructions={ __(
121-
'Fill the URL and the title of the iframe.',
151+
'Fill the iframe source and the title of the iframe.',
122152
'blockparty-iframe'
123153
) }
124154
>
125155
<div style={ { width: '100%' } }>
126156
<TextControl
127-
label={ __( 'URL', 'blockparty-iframe' ) }
157+
label={ __( 'Source', 'blockparty-iframe' ) }
128158
value={ iframeData.url }
129-
onChange={ ( value ) =>
130-
setIframeData( { ...iframeData, url: value } )
131-
}
132-
placeholder="https://..."
133-
type="url"
134-
help={
135-
iframeData.url.length &&
136-
! isURL( iframeData.url )
137-
? __(
138-
'The URL is invalid.',
139-
'blockparty-iframe'
140-
)
141-
: ''
142-
}
159+
onChange={ handleUrlChange }
160+
placeholder={ `https://... or <iframe src="https://..."` }
161+
help={ __(
162+
'You can either paste a URL or the iframe code.',
163+
'blockparty-iframe'
164+
) }
143165
/>
144166

145167
<TextControl
@@ -170,6 +192,9 @@ export default function Edit( { attributes, setAttributes } ) {
170192
title={ iframeData.title }
171193
src={ iframeData.url }
172194
loading={ lazyload ? 'lazy' : 'eager' }
195+
{ ...convertAttributesToProps(
196+
iframeData.iframeAttributes
197+
) }
173198
/>
174199
) }
175200
</div>

src/blockparty-iframe/save.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
* @see https://developer.wordpress.org/block-editor/reference-guides/packages/packages-block-editor/#useblockprops
66
*/
77
import { useBlockProps } from '@wordpress/block-editor';
8+
import { convertAttributesToProps } from './utils';
89

910
/**
1011
* The save function defines the way in which the different attributes should
@@ -16,7 +17,7 @@ import { useBlockProps } from '@wordpress/block-editor';
1617
* @return {Element} Element to render.
1718
*/
1819
export default function save( { attributes } ) {
19-
const { lazyload, title, url } = attributes;
20+
const { lazyload, title, url, iframeAttributes } = attributes;
2021

2122
if ( ! url || ! title ) {
2223
return <div { ...useBlockProps.save() } />;
@@ -28,6 +29,7 @@ export default function save( { attributes } ) {
2829
title={ title }
2930
src={ url }
3031
loading={ lazyload ? 'lazy' : 'eager' }
32+
{ ...convertAttributesToProps( iframeAttributes ) }
3133
/>
3234
</div>
3335
);

src/blockparty-iframe/style.scss

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,9 @@
1111
width: 100%;
1212
height: 100%;
1313
}
14+
15+
&:not(.has-aspect-ratio) {
16+
aspect-ratio: 1;
17+
min-height: unset;
18+
}
1419
}

src/blockparty-iframe/utils.js

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
/**
2+
* Utility functions for iframe attribute handling.
3+
*/
4+
5+
/**
6+
* Map HTML attribute names to React prop names.
7+
*
8+
* @param {string} attributeName - The HTML attribute name.
9+
* @return {string} The React prop name.
10+
*/
11+
export function mapHtmlAttributeToReact( attributeName ) {
12+
const attributeMap = {
13+
allowfullscreen: 'allowFullScreen',
14+
allowpaymentrequest: 'allowPaymentRequest',
15+
referrerpolicy: 'referrerPolicy',
16+
};
17+
18+
return attributeMap[ attributeName.toLowerCase() ] || attributeName;
19+
}
20+
21+
/**
22+
* Check if an attribute is a boolean HTML attribute.
23+
*
24+
* @param {string} attributeName - The name of the attribute to check.
25+
* @return {boolean} True if the attribute is boolean, false otherwise.
26+
*/
27+
export function isBooleanAttribute( attributeName ) {
28+
const booleanAttrs = [ 'allowfullscreen', 'allowpaymentrequest' ];
29+
30+
return booleanAttrs.includes( attributeName.toLowerCase() );
31+
}
32+
33+
/**
34+
* Convert iframe attributes array to props object for React.
35+
* Handles boolean attributes and React prop name mapping correctly.
36+
*
37+
* @param {Array} attributes - Array of {key, value} objects.
38+
* @return {Object} Props object for React component.
39+
*/
40+
export function convertAttributesToProps( attributes ) {
41+
return Object.fromEntries(
42+
( attributes || [] ).map( ( attr ) => {
43+
// Map HTML attribute name to React prop name
44+
const propName = mapHtmlAttributeToReact( attr.key );
45+
46+
// Convert 'true' string to boolean for boolean attributes
47+
const value =
48+
isBooleanAttribute( attr.key ) && attr.value === 'true'
49+
? true
50+
: attr.value;
51+
52+
return [ propName, value ];
53+
} )
54+
);
55+
}
56+
57+
/**
58+
* Check if an iframe attribute should be excluded.
59+
*
60+
* @param {string} attributeName - The name of the attribute to check.
61+
* @return {boolean} True if the attribute should be excluded, false otherwise.
62+
*/
63+
export function isExcludedIframeAttribute( attributeName ) {
64+
const excludedAttrs = [
65+
'src', // Managed separately
66+
'loading', // Managed by lazyload option
67+
'title', // Managed separately
68+
'width', // Managed by block dimension supports
69+
'height', // Managed by block dimension supports
70+
'style', // Requires object format in React, not string
71+
'frameborder', // Deprecated HTML attribute
72+
'marginwidth', // Deprecated HTML attribute
73+
'marginheight', // Deprecated HTML attribute
74+
'scrolling', // Deprecated HTML attribute
75+
'align', // Deprecated HTML attribute
76+
'longdesc', // Deprecated HTML attribute
77+
'name', // Can cause conflicts
78+
];
79+
80+
return excludedAttrs.includes( attributeName.toLowerCase() );
81+
}
82+
83+
/**
84+
* Parse iframe HTML code and extract src URL, title, and attributes.
85+
*
86+
* @param {string} value - The value that could be a URL or iframe HTML code.
87+
* @return {Object|null} Object with url, title, and attributes array, or null if not an iframe.
88+
*/
89+
export function parseIframeCode( value ) {
90+
// Check if the value contains iframe tag
91+
const iframeRegex = /<iframe[^>]*>/i;
92+
const match = value.match( iframeRegex );
93+
94+
if ( ! match ) {
95+
return null;
96+
}
97+
98+
// Extract only the opening tag (ignore any content inside iframe)
99+
const iframeTag = match[ 0 ];
100+
101+
// Create a temporary DOM element to parse the HTML
102+
// Use a self-closing iframe to avoid parsing issues with content
103+
const tempDiv = document.createElement( 'div' );
104+
tempDiv.insertAdjacentHTML( 'beforeend', iframeTag + '</iframe>' );
105+
const iframeElement = tempDiv.querySelector( 'iframe' );
106+
107+
if ( ! iframeElement ) {
108+
return null;
109+
}
110+
111+
// Extract src attribute
112+
const src = iframeElement.getAttribute( 'src' ) || '';
113+
114+
// Extract title attribute
115+
const title = iframeElement.getAttribute( 'title' ) || '';
116+
117+
// Extract all other attributes (excluding managed and deprecated ones)
118+
const attributes = [];
119+
120+
for ( const attr of iframeElement.attributes ) {
121+
if ( ! isExcludedIframeAttribute( attr.name ) ) {
122+
// For boolean attributes, store 'true' as value if present
123+
const value = isBooleanAttribute( attr.name ) ? 'true' : attr.value;
124+
125+
attributes.push( {
126+
key: attr.name,
127+
value,
128+
} );
129+
}
130+
}
131+
132+
return {
133+
url: src,
134+
title,
135+
attributes,
136+
};
137+
}

0 commit comments

Comments
 (0)