PluginProbe ʕ •ᴥ•ʔ
VK Blocks / trunk
VK Blocks vtrunk
1.122.0 1.121.0 1.120.0 1.119.2 1.119.1 1.119.0 1.118.7 1.82.0.0 1.82.0.1 1.83.0.0 1.83.0.1 1.84.0.0 1.84.0.1 1.85.0.2 1.85.0.3 1.85.1.0 1.85.1.1 1.86.0.0 1.86.0.1 1.86.1.0 1.87.0.0 1.87.0.1 1.88.0.1 1.88.0.2 1.89.0.0 1.9.2 1.90.0.0 1.90.0.1 1.90.1.1 1.91.0.0 1.91.1.1 1.92.0.0 1.92.0.1 1.92.1.0 1.92.1.1 1.93.0.0 trunk 1.93.0.1 0.1.1 1.93.1.0 0.10.3 1.93.1.1 0.11.0 1.94.0.0 0.12.6 1.94.0.1 0.15.1 1.94.1.0 0.16.2 1.94.2.1 0.17.6 1.94.2.2 0.2.2 1.95.0.3 0.22.4 1.95.0.4 0.26.7 1.96.2.0 0.3.2 1.96.2.1 0.31.0 1.97.0.1 0.35.5 1.97.0.2 0.37.5 1.99.0.0 0.38.8 1.99.0.1 0.39.4 0.4.7 0.41.0 0.42.0 0.44.13 0.45.2 0.46.1 0.47.0 0.48.0 0.49.8 0.5.2 0.50.3 0.51.0 0.52.2 0.53.2 0.54.0 0.55.0 0.56.2 0.56.3 0.57.6 0.58.10 0.59.0 0.6.0 0.60.1 0.61.2 0.7.1 0.8.2 1.0.16 1.10.0 1.100.0.0 1.100.0.1 1.102.0.0 1.102.0.1 1.103.0.0 1.104.0.0 1.104.0.1 1.105.1.0 1.105.1.1 1.106.0.0 1.106.0.1 1.107.0.0 1.107.0.1 1.107.0.2 1.108.0.0 1.108.0.1 1.109.0.0 1.109.0.1 1.11.4 1.110.0.0 1.110.0.1 1.111.0.0 1.111.0.1 1.111.0.2 1.112.0.0 1.112.0.1 1.113.0.0 1.113.0.1 1.114.0.0 1.114.0.1 1.114.2.0 1.114.2.1 1.115.0.0 1.115.0.1 1.115.1.0 1.115.1.1 1.115.2.0 1.115.2.1 1.116.0.0 1.116.0.1 1.116.1.0 1.116.1.1 1.116.2.0 1.117.0.0 1.117.0.1 1.117.1.0 1.118.2 1.118.5 1.12.0 1.13.2 1.14.1 1.15.1 1.16.11 1.17.0 1.18.6 1.19.0 1.19.1 1.2.3 1.20.7 1.21.0 1.22.4 1.23.0 1.24.5 1.25.1 1.26.2 1.27.7.2 1.28.0.1 1.29.2.0 1.3.9 1.30.0.1 1.31.0.1 1.32.0.2 1.33.2.1 1.36.1.5 1.37.0.0 1.39.2.1 1.4.6 1.40.0.1 1.40.1.0 1.40.1.1 1.41.0.0 1.41.0.1 1.41.2.2 1.41.2.3 1.43.0.0 1.43.0.1 1.43.0.2 1.44.0.0 1.44.0.1 1.45.0.0 1.45.0.1 1.46.0.0 1.46.0.1 1.47.0.0 1.47.0.1 1.47.1.0 1.48.0.0 1.48.0.1 1.48.0.2 1.48.1.0 1.48.1.1 1.5.0 1.50.0.0 1.50.0.1 1.51.0.0 1.51.0.1 1.52.0.0 1.52.0.1 1.53.0.0 1.53.0.1 1.54.0.0 1.54.0.1 1.55.0.0 1.55.0.1 1.56.0.0 1.56.0.1 1.57.0.0 1.57.0.2 1.57.0.3 1.57.0.4 1.57.0.5 1.57.1.0 1.57.1.1 1.57.1.2 1.58.0.0 1.58.0.1 1.59.0.0 1.59.0.1 1.6.0 1.60.0.0 1.60.0.1 1.63.0.0 1.63.0.1 1.64.0.0 1.64.0.1 1.64.1.0 1.64.1.2 1.67.0.0 1.67.0.1 1.68.0.0 1.68.0.1 1.69.0.0 1.69.0.1 1.69.1.1 1.69.1.2 1.7.1 1.70.0.0 1.70.0.1 1.71.0.0 1.71.0.1 1.72.0.0 1.72.1.0 1.72.1.1 1.73.0.0 1.73.0.1 1.74.0.0 1.74.0.1 1.75.0.0 1.75.1.0 1.75.1.1 1.76.0.0 1.76.0.1 1.76.1.0 1.76.1.1 1.76.2.0 1.76.2.1 1.77.0.0 1.77.0.1 1.78.0.0 1.78.0.1 1.79.0.0 1.79.0.1 1.79.0.2 1.79.0.3 1.79.1.0 1.79.1.1 1.8.2 1.80.1.0 1.80.1.1 1.80.1.2 1.81.0.1 1.81.0.2
vk-blocks / src / blocks / visual-embed / edit.js
vk-blocks / src / blocks / visual-embed Last commit date
utils 1 week ago block.json 2 months ago edit.js 1 week ago icon.svg 2 months ago index.js 2 months ago index.php 2 months ago save.js 2 months ago style.scss 1 week ago
edit.js
336 lines
1 /* global vkBlocksVisualEmbed */
2 /* vkBlocksVisualEmbed は PHP 側で wp_localize_script により注�
3 �されるグローバル */
4 import { __ } from '@wordpress/i18n';
5 import {
6 InspectorControls,
7 useBlockProps,
8 BlockControls,
9 } from '@wordpress/block-editor';
10
11 import {
12 PanelBody,
13 TextareaControl,
14 TextControl,
15 Notice,
16 } from '@wordpress/components';
17 import { useEffect, useRef, useState } from '@wordpress/element';
18
19 import { getCssLength } from './utils/get-css-length';
20 import { isAllowedSrc as isAllowedSrcByPatterns } from './utils/match-url-pattern';
21 import { getYouTubePreviewData } from './utils/youtube-preview';
22
23 const allowedUrlPatterns =
24 typeof vkBlocksVisualEmbed !== 'undefined' &&
25 vkBlocksVisualEmbed.allowedUrlPatterns
26 ? vkBlocksVisualEmbed.allowedUrlPatterns
27 : [];
28
29 // 許可するURLパターンの�
30 �列
31 const ALLOWED_URL_PATTERNS = [
32 'https://*.google.com/*',
33 'https://*.youtube.com/embed/*',
34 'https://www.openstreetmap.org/export/*',
35 'https://player.vimeo.com/*',
36 ];
37
38 // フィルターフックを使用してURLパターンを変更
39 const filteredAllowedUrlPatterns = [
40 ...ALLOWED_URL_PATTERNS,
41
42 ...allowedUrlPatterns,
43 ];
44
45 export default function EmbedCodeEdit({ attributes, setAttributes }) {
46 const { iframeCode, iframeWidth, iframeHeight } = attributes;
47 const [tempIframeCode, setTempIframeCode] = useState(iframeCode);
48 const prevIframeWidth = useRef(attributes.iframeWidth);
49
50 useEffect(() => {
51 // align がユーザーによって設定されていたら変更しない
52 if (attributes.align !== undefined) {
53 return;
54 }
55
56 const isFullWidth = String(attributes.iframeWidth) === '100%';
57 const wasFullWidth = String(prevIframeWidth.current) === '100%';
58
59 // 100% から 100% 以外に変わった瞬間のみ align: "center" を適用
60 if (wasFullWidth && !isFullWidth) {
61 setAttributes({ align: 'center' });
62 }
63
64 // iframeWidth の変更を記録
65 prevIframeWidth.current = attributes.iframeWidth;
66 }, [attributes.iframeWidth]);
67
68 useEffect(() => {
69 if (iframeWidth && isIframe) {
70 updateIframeAttributes(iframeWidth, iframeHeight);
71 }
72 }, [iframeWidth, iframeHeight]);
73
74 // iframeを解析する関数
75 const parseIframeCode = (code) => {
76 const parser = new window.DOMParser();
77 const doc = parser.parseFromString(code, 'text/html');
78 const iframe = doc.querySelector('iframe');
79 return iframe ? iframe : false;
80 };
81 const [isIframe, setIsIframe] = useState(!!parseIframeCode(iframeCode));
82
83 // 外部からの属性変更(RTC・Undo/Redo等)をローカルstateに反映
84 useEffect(() => {
85 setTempIframeCode(iframeCode);
86 setIsIframe(!!parseIframeCode(iframeCode));
87 }, [iframeCode]);
88
89 const blockProps = useBlockProps({
90 className: 'vk-visual-embed',
91 });
92 const youtubePreviewData = getYouTubePreviewData(iframeCode);
93 const youtubePreviewStyle = {
94 width: getCssLength(iframeWidth),
95 height: getCssLength(iframeHeight),
96 };
97
98 // iframeのsrc属性を検証する関数(許可パターン判定は utils に切り出し済み)
99 const isAllowedSrc = (src) =>
100 isAllowedSrcByPatterns(src, filteredAllowedUrlPatterns);
101
102 // iframeタグ以外を削除する関数
103 const sanitizeIframeCode = (code) => {
104 if (!code) {
105 return '';
106 }
107
108 // DOMParserが利用できない環境の場合は�
109 �力をそのまま返す
110 if (typeof window.DOMParser === 'undefined') {
111 return code;
112 }
113
114 const iframe = parseIframeCode(code);
115
116 if (!iframe) {
117 return '';
118 }
119
120 // src属性を検証
121 const src = iframe.getAttribute('src');
122 if (!isAllowedSrc(src)) {
123 return __('Only allowed URLs can be embedded.', 'vk-blocks');
124 }
125
126 return iframe.outerHTML;
127 };
128
129 // iframeの属性を解析して�
130 と高さを取得
131 const extractIframeAttributes = (code) => {
132 if (typeof window.DOMParser === 'undefined') {
133 return false;
134 }
135
136 const iframe = parseIframeCode(code);
137
138 if (iframe) {
139 const newWidth = iframe.getAttribute('width') || iframeWidth;
140 const newHeight = iframe.getAttribute('height') || iframeHeight;
141
142 // 抽出した値を設定パネルに反映
143 setAttributes({
144 iframeWidth: newWidth,
145 iframeHeight: newHeight,
146 });
147
148 return true; // iframeが見つかった場合はtrueを返す
149 }
150
151 return false; // iframeが見つからない場合はfalseを返す
152 };
153
154 // iframeの属性を解析・更新する関数
155 const updateIframeAttributes = (newWidth, newHeight) => {
156 if (!iframeCode || typeof window.DOMParser === 'undefined') {
157 return;
158 }
159
160 const iframe = parseIframeCode(iframeCode);
161
162 if (iframe) {
163 if (newWidth) {
164 iframe.setAttribute('width', newWidth);
165 }
166 if (newHeight) {
167 iframe.setAttribute('height', newHeight);
168 }
169
170 // 更新後のiframeコードを設定
171 setAttributes({
172 iframeCode: iframe.outerHTML,
173 iframeWidth: newWidth || iframeWidth,
174 iframeHeight: newHeight || iframeHeight,
175 });
176 setTempIframeCode(iframe.outerHTML);
177 }
178 };
179
180 return (
181 <div {...blockProps}>
182 <BlockControls />
183 <InspectorControls>
184 <PanelBody title={__('Embed Code Settings', 'vk-blocks')}>
185 <TextareaControl
186 label={__('Embed Code', 'vk-blocks')}
187 value={tempIframeCode}
188 onChange={(newCode) => {
189 setTempIframeCode(newCode);
190 }}
191 onBlur={() => {
192 if (!tempIframeCode) {
193 setAttributes({ iframeCode: '' });
194 setIsIframe(false);
195 return;
196 }
197 const sanitizedCode =
198 sanitizeIframeCode(tempIframeCode);
199 setAttributes({ iframeCode: sanitizedCode });
200 if (sanitizedCode) {
201 extractIframeAttributes(sanitizedCode);
202 }
203
204 setIsIframe(!!parseIframeCode(sanitizedCode));
205 setTempIframeCode(sanitizedCode);
206 }}
207 help={__(
208 'Please paste the iframe embed code directly. Only iframe tags with allowed URLs (Google Maps, Google Calendar, Google Forms, YouTube、OpenStreetMap, Vimeo) are permitted.',
209 'vk-blocks'
210 )}
211 />
212 {!iframeCode && (
213 <Notice
214 status="error"
215 isDismissible={false}
216 className="vk-visual-embed_notice"
217 >
218 {__(
219 'Please enter an iframe embed code.',
220 'vk-blocks'
221 )}
222 </Notice>
223 )}
224 {iframeCode && !sanitizeIframeCode(iframeCode) && (
225 <Notice
226 status="error"
227 isDismissible={false}
228 className="vk-visual-embed_notice"
229 >
230 {__(
231 'The provided URL is not allowed. Please use an approved embed source.',
232 'vk-blocks'
233 )}
234 </Notice>
235 )}
236 <TextControl
237 label={__('Iframe Width', 'vk-blocks')}
238 value={iframeWidth}
239 onChange={(newWidth) => {
240 setAttributes({ iframeWidth: newWidth });
241 }}
242 onBlur={() => {
243 if (!iframeWidth) {
244 extractIframeAttributes(iframeCode);
245 return;
246 }
247 if (/^\d+(px|%)?$/.test(iframeWidth)) {
248 updateIframeAttributes(
249 iframeWidth,
250 iframeHeight
251 );
252 } else {
253 setAttributes({ iframeWidth: '' });
254 updateIframeAttributes('', iframeHeight);
255 }
256 }}
257 disabled={!isIframe}
258 />
259 <TextControl
260 label={__('Iframe Height', 'vk-blocks')}
261 value={iframeHeight}
262 onChange={(newHeight) => {
263 setAttributes({ iframeHeight: newHeight });
264 }}
265 onBlur={() => {
266 if (!iframeHeight) {
267 extractIframeAttributes(iframeCode);
268 return;
269 }
270 if (/^\d+(px|%)?$/.test(iframeHeight)) {
271 updateIframeAttributes(
272 iframeWidth,
273 iframeHeight
274 );
275 } else {
276 setAttributes({ iframeHeight: '' });
277 updateIframeAttributes(iframeWidth, '');
278 }
279 }}
280 disabled={!isIframe}
281 />
282 {!isIframe && (
283 <Notice status="warning" isDismissible={false}>
284 {__(
285 'Note: These settings are only applicable to iframe tags. Other embed codes will not respond to these adjustments.',
286 'vk-blocks'
287 )}
288 </Notice>
289 )}
290 </PanelBody>
291 </InspectorControls>
292 <div style={{ position: 'relative' }}>
293 {iframeCode && youtubePreviewData && (
294 <div
295 className="vk-visual-embed-preview vk-visual-embed-preview--youtube"
296 style={youtubePreviewStyle}
297 title={__(
298 'Preview only. The video plays on the published page.',
299 'vk-blocks'
300 )}
301 >
302 <img
303 className="vk-visual-embed-preview__youtube-thumbnail"
304 src={youtubePreviewData.thumbnailUrl}
305 alt={__('YouTube video preview', 'vk-blocks')}
306 decoding="async"
307 />
308 <span
309 className="vk-visual-embed-preview__youtube-play"
310 aria-hidden="true"
311 >
312 <svg
313 className="vk-visual-embed-preview__youtube-play-icon"
314 viewBox="0 0 17 20"
315 xmlns="http://www.w3.org/2000/svg"
316 focusable="false"
317 >
318 <path d="M0 0 L17 10 L0 20 Z" />
319 </svg>
320 </span>
321 </div>
322 )}
323 {iframeCode && !youtubePreviewData && (
324 <div
325 className="vk-visual-embed-preview"
326 dangerouslySetInnerHTML={{ __html: iframeCode }}
327 style={{
328 pointerEvents: 'none',
329 }}
330 />
331 )}
332 </div>
333 </div>
334 );
335 }
336