html-to-gutenberg 4.2.1
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/LICENSE.MD +21 -0
- package/globals.js +32 -0
- package/globals.ts +26 -0
- package/index.js +1589 -0
- package/index.ts +1119 -0
- package/package.json +55 -0
- package/readme.md +116 -0
- package/tsconfig.json +13 -0
package/index.js
ADDED
|
@@ -0,0 +1,1589 @@
|
|
|
1
|
+
import presetReact from '@babel/preset-react';
|
|
2
|
+
import { transform } from '@svgr/core';
|
|
3
|
+
import * as babel from '@babel/core';
|
|
4
|
+
import * as cheerio from 'cheerio';
|
|
5
|
+
import scopeCss from 'css-scoping';
|
|
6
|
+
import extractAssets from 'fetch-page-assets';
|
|
7
|
+
import fs from 'fs';
|
|
8
|
+
import icon from 'html-screenshots';
|
|
9
|
+
import imageToBase64 from 'image-to-base64';
|
|
10
|
+
import { createRequire } from 'module';
|
|
11
|
+
import convert from 'node-html-to-jsx';
|
|
12
|
+
import path from 'path';
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
imports,
|
|
16
|
+
images,
|
|
17
|
+
characters
|
|
18
|
+
} from './globals.js';
|
|
19
|
+
|
|
20
|
+
const require = createRequire(import.meta.url);
|
|
21
|
+
const { version } = require('./package.json');
|
|
22
|
+
|
|
23
|
+
const block = async (
|
|
24
|
+
htmlContent,
|
|
25
|
+
options = {
|
|
26
|
+
name: 'My block',
|
|
27
|
+
prefix: 'wp',
|
|
28
|
+
category: 'common',
|
|
29
|
+
basePath: process.cwd(),
|
|
30
|
+
shouldSaveFiles: true,
|
|
31
|
+
generateIconPreview: false,
|
|
32
|
+
jsFiles: [],
|
|
33
|
+
cssFiles: [],
|
|
34
|
+
source: null,
|
|
35
|
+
}
|
|
36
|
+
) => {
|
|
37
|
+
const panels = [];
|
|
38
|
+
const styles = [];
|
|
39
|
+
const scripts = [];
|
|
40
|
+
const attributes = {};
|
|
41
|
+
const formVars = {};
|
|
42
|
+
|
|
43
|
+
const { name, prefix, source } = options;
|
|
44
|
+
|
|
45
|
+
let js = '';
|
|
46
|
+
let css = '';
|
|
47
|
+
let phpEmailData = '';
|
|
48
|
+
let emailTemplate = '';
|
|
49
|
+
|
|
50
|
+
function hasTailwindCdnSource(jsFiles) {
|
|
51
|
+
const tailwindCdnRegex = /https:\/\/(cdn\.tailwindcss\.com(\?[^"'\s]*)?|cdn\.jsdelivr\.net\/npm\/@tailwindcss\/browser@4(\.\d+){0,2})/;
|
|
52
|
+
|
|
53
|
+
return jsFiles.some(url => tailwindCdnRegex.test(url));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function parseInlineStyle(styleString) {
|
|
57
|
+
if (!styleString || typeof styleString !== 'string') return '';
|
|
58
|
+
|
|
59
|
+
const styleEntries = styleString
|
|
60
|
+
.split(';')
|
|
61
|
+
.map(rule => rule.trim())
|
|
62
|
+
.filter(Boolean)
|
|
63
|
+
.map(rule => {
|
|
64
|
+
const [property, value] = rule.split(':').map(part => part.trim());
|
|
65
|
+
|
|
66
|
+
if (!property || !value) return null;
|
|
67
|
+
|
|
68
|
+
const camelCasedProperty = property.replace(/-([a-z])/g, (_, char) => char.toUpperCase());
|
|
69
|
+
|
|
70
|
+
return [camelCasedProperty, value];
|
|
71
|
+
})
|
|
72
|
+
.filter(Boolean);
|
|
73
|
+
|
|
74
|
+
const styleObject = Object.fromEntries(styleEntries);
|
|
75
|
+
|
|
76
|
+
return JSON.stringify(styleObject).replace(/"([^"]+)":/g, '$1:');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function sanitizeAndReplaceLeadingNumbers(input) {
|
|
80
|
+
const numberWords = ['zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine'];
|
|
81
|
+
let hasReplacedFirstNumber = false;
|
|
82
|
+
|
|
83
|
+
return input
|
|
84
|
+
.toLowerCase()
|
|
85
|
+
.replace(/[\s\-_]/g, '')
|
|
86
|
+
.replace(/\d/g, (digit) => {
|
|
87
|
+
if (hasReplacedFirstNumber) return digit;
|
|
88
|
+
|
|
89
|
+
hasReplacedFirstNumber = true;
|
|
90
|
+
return numberWords[parseInt(digit)] + digit;
|
|
91
|
+
})
|
|
92
|
+
.replace(/^[^a-z]+/, '');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const replaceUnderscoresSpacesAndUppercaseLetters = (input = '') => {
|
|
96
|
+
const nonWordOrUnderscore = /\W|_/g;
|
|
97
|
+
return input.replace(nonWordOrUnderscore, '-').toLowerCase();
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const buildFilePath = (basePath, fileName) => {
|
|
101
|
+
return path.join(basePath, fileName);
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const writeFile = (filePath, data) => {
|
|
105
|
+
fs.writeFileSync(filePath, data);
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const saveFile = (fileName, contents, options) => {
|
|
109
|
+
try {
|
|
110
|
+
const filePath = buildFilePath(options.basePath, fileName);
|
|
111
|
+
writeFile(filePath, contents);
|
|
112
|
+
return contents;
|
|
113
|
+
} catch (error) {
|
|
114
|
+
logError(error);
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
function buildRelativeUrlRegex() {
|
|
119
|
+
const attributes = [
|
|
120
|
+
'src',
|
|
121
|
+
'href',
|
|
122
|
+
'action',
|
|
123
|
+
'srcset',
|
|
124
|
+
'poster',
|
|
125
|
+
'data',
|
|
126
|
+
'formaction'
|
|
127
|
+
];
|
|
128
|
+
|
|
129
|
+
const protocolsToIgnore = ['https?:', '//', 'mailto:', 'tel:', '#'].join('|');
|
|
130
|
+
|
|
131
|
+
return new RegExp(
|
|
132
|
+
`\\b(${attributes.join('|')}|data-[a-zA-Z0-9_-]+)\\s*=\\s*(['"])(?!${protocolsToIgnore})([^'"]+)\\2`,
|
|
133
|
+
'gi'
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function replaceRelativeUrls(html, replacer) {
|
|
138
|
+
const regex = buildRelativeUrlRegex();
|
|
139
|
+
return html.replace(regex, (_match, attribute, quote, url) => {
|
|
140
|
+
const updatedUrl = replacer(url);
|
|
141
|
+
return `${attribute}=${quote}${updatedUrl}${quote}`;
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function getRelativeUrlCssRegex() {
|
|
146
|
+
const ignoredProtocols = ['https?:', '//', 'data:', 'mailto:', 'tel:', '#'].join('|');
|
|
147
|
+
return new RegExp(
|
|
148
|
+
`url\\(\\s*(['"]?)(?!${ignoredProtocols})([^'")]+)\\1\\s*\\)`,
|
|
149
|
+
'gi'
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function replaceRelativeUrlsInCss(css, replacer) {
|
|
154
|
+
const regex = getRelativeUrlCssRegex();
|
|
155
|
+
|
|
156
|
+
return css.replace(regex, (_match, quote, url) => {
|
|
157
|
+
const updatedUrl = replacer(url);
|
|
158
|
+
return `url(${quote}${updatedUrl}${quote})`;
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function resolveUrl(relativePath, baseUrl) {
|
|
163
|
+
return new URL(relativePath, baseUrl).href;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function replaceRelativeUrlsInHtml(html, baseUrl) {
|
|
167
|
+
return replaceRelativeUrls(html, (url) => resolveUrl(url, baseUrl));
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function replaceRelativeUrlsInCssWithBase(css, cssFileUrl) {
|
|
171
|
+
return replaceRelativeUrlsInCss(css, (url) => resolveUrl(url, cssFileUrl));
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const parseRequirements = async (files, options) => {
|
|
175
|
+
const { source } = options;
|
|
176
|
+
let output = '';
|
|
177
|
+
|
|
178
|
+
for (const file of files) {
|
|
179
|
+
try {
|
|
180
|
+
const response = await fetch(file);
|
|
181
|
+
|
|
182
|
+
if (!response.ok) {
|
|
183
|
+
throw new Error(`HTTP error! Status: ${response.status}`);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
let data = await response.text();
|
|
187
|
+
|
|
188
|
+
if (source) {
|
|
189
|
+
data = replaceRelativeUrlsInCssWithBase(data, file);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
output += `${data}\n\n`;
|
|
193
|
+
} catch (error) {
|
|
194
|
+
logError(error);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return output;
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
const convertDashesSpacesAndUppercaseToUnderscoresAndLowercase = (input = '') => {
|
|
202
|
+
if (!input) return '';
|
|
203
|
+
|
|
204
|
+
return input
|
|
205
|
+
.replaceAll('-', '_')
|
|
206
|
+
.replaceAll(' ', '_')
|
|
207
|
+
.toLowerCase();
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
const newName = replaceUnderscoresSpacesAndUppercaseLetters(name);
|
|
211
|
+
const blockName = `${sanitizeAndReplaceLeadingNumbers(replaceUnderscoresSpacesAndUppercaseLetters(prefix))}/${sanitizeAndReplaceLeadingNumbers(replaceUnderscoresSpacesAndUppercaseLetters(name))}`;
|
|
212
|
+
const blockNameHandle = `${prefix}-${newName}`;
|
|
213
|
+
|
|
214
|
+
const getPhp = (options) => {
|
|
215
|
+
const { name, prefix, jsFiles, cssFiles } = options;
|
|
216
|
+
const phpName = convertDashesSpacesAndUppercaseToUnderscoresAndLowercase(name);
|
|
217
|
+
const phpPrefix = `${functionSufix}${convertDashesSpacesAndUppercaseToUnderscoresAndLowercase(prefix)}`;
|
|
218
|
+
const tailwindFix = hasTailwindCdnSource(jsFiles) ? 'function forceTailwindUpdate(){let e=setInterval(()=>{if("undefined"!=typeof tailwind){clearInterval(e);let n=document.documentElement.outerHTML;tailwind.config.content=[{raw:n,extension:"html"}]}},100)}forceTailwindUpdate();': '';
|
|
219
|
+
const tailwindFooter = hasTailwindCdnSource(jsFiles) ? '(()=>{if(window.__tailwindObserverActive)return;window.__tailwindObserverActive=!0;let e=new MutationObserver(()=>{let t=[...document.querySelectorAll("style")].find(e=>e.innerText.includes("--tw-"));t&&(document.body.appendChild(t),e.disconnect(),window.__tailwindObserverActive=!1)});e.observe(document.documentElement,{childList:!0,subtree:!0})})();' : '';
|
|
220
|
+
const inlineRemoteLoader = `var remoteUrls = ${JSON.stringify(jsFiles)};(function loadScripts() {window._loadedRemoteScripts = window._loadedRemoteScripts || new Set();const style = document.createElement('style');remoteUrls.forEach((url) => {if (window._loadedRemoteScripts.has(url)) return;const script = document.createElement('script');script.src = url;document.head.appendChild(script);window._loadedRemoteScripts.add(url);});})();${tailwindFix}${tailwindFooter}`;
|
|
221
|
+
|
|
222
|
+
const enqueueRemoteStyles = cssFiles
|
|
223
|
+
.map((remoteUrl) => {
|
|
224
|
+
return `
|
|
225
|
+
wp_enqueue_style(
|
|
226
|
+
'${blockNameHandle}-${remoteUrl
|
|
227
|
+
.split('/')
|
|
228
|
+
.pop()
|
|
229
|
+
.replace(/\.\w+$/, '')}',
|
|
230
|
+
'${remoteUrl}',
|
|
231
|
+
array(),
|
|
232
|
+
null
|
|
233
|
+
);
|
|
234
|
+
`;
|
|
235
|
+
})
|
|
236
|
+
.join('\n');
|
|
237
|
+
|
|
238
|
+
return `<?php
|
|
239
|
+
/*
|
|
240
|
+
* Plugin Name: ${name}
|
|
241
|
+
* Version: ${version}
|
|
242
|
+
* Author: Html to Gutenberg
|
|
243
|
+
* Author URI: https://www.html-to-gutenberg.io/
|
|
244
|
+
* Description: A custom editable block built with Html to Gutenberg
|
|
245
|
+
|
|
246
|
+
*/
|
|
247
|
+
|
|
248
|
+
if ( ! defined( 'ABSPATH' ) ) {
|
|
249
|
+
exit;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function parse_form_placeholders_${functionSufix}($content, $post_data)
|
|
253
|
+
{
|
|
254
|
+
return preg_replace_callback('/{{(.*?)}}/', function ($matches) use ($post_data) {
|
|
255
|
+
$key = trim($matches[1]);
|
|
256
|
+
return isset($post_data[$key]) ? sanitize_text_field($post_data[$key]) : '';
|
|
257
|
+
}, $content);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
add_action('wp_ajax_send_test_email_${functionSufix}}', function() {
|
|
261
|
+
//check_ajax_referer('wp_rest');
|
|
262
|
+
|
|
263
|
+
$post_data = $_POST;
|
|
264
|
+
$to = sanitize_email(parse_form_placeholders_${functionSufix}($post_data['to'], $post_data));
|
|
265
|
+
$from = sanitize_email(parse_form_placeholders_${functionSufix}($post_data['from'], $post_data));
|
|
266
|
+
$subject = sanitize_text_field(parse_form_placeholders_${functionSufix}($post_data['subject'], $post_data));
|
|
267
|
+
$message = wp_kses_post(parse_form_placeholders_${functionSufix}($post_data['message'], $post_data));
|
|
268
|
+
|
|
269
|
+
if (empty($to) || empty($from)) {
|
|
270
|
+
wp_send_json_error('Missing email addresses.');
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
$sent = wp_mail($to, $subject, $message, [
|
|
274
|
+
'Content-Type: text/html; charset=UTF-8',
|
|
275
|
+
'From: ' . $from
|
|
276
|
+
]);
|
|
277
|
+
|
|
278
|
+
if ($sent) {
|
|
279
|
+
wp_send_json_success('Email sent');
|
|
280
|
+
} else {
|
|
281
|
+
error_log('Failed to send. To: ' . $to . ' | From: ' . $from);
|
|
282
|
+
wp_send_json_error('Failed to send email.');
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
${phpEmailData}
|
|
287
|
+
|
|
288
|
+
function ${phpPrefix}_${phpName}_add_custom_editor_styles() {
|
|
289
|
+
echo '<style>span.block-editor-rich-text__editable.rich-text{all:unset!important}a br[data-rich-text-line-break=true],span.block-editor-block-icon.block-editor-block-switcher__toggle.has-colors img{display:none}.block-editor-block-types-list__list-item{width:100%!important}.block-editor-block-list__layout.is-root-container>:where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width:100%;margin:0}[aria-label="Empty block; start writing or type forward slash to choose a block"]{max-width:1200px!important}span.block-editor-block-types-list__item-icon img{max-width:100%;width:100%;margin:0;display:block}span.block-editor-block-icon.has-colors{all:inherit;order:2;flex:0 0 100%;width:100%}span.block-editor-block-icon.has-colors svg{margin-left:auto;margin-right:auto}.block-editor-block-card{display:flex!important;flex-wrap:wrap}.block-editor-inserter__preview-content-missing{display:none!important}</style>';
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
add_action('admin_footer', function () {
|
|
293
|
+
$screen = get_current_screen();
|
|
294
|
+
|
|
295
|
+
if ($screen && method_exists($screen, 'is_block_editor') && $screen->is_block_editor()) {
|
|
296
|
+
$href = esc_url(plugins_url('editor.css', __FILE__));
|
|
297
|
+
echo "<link rel='stylesheet' id='${blockNameHandle}-style' href='$href' type='text/css' media='all' />";
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
add_action( 'enqueue_block_editor_assets', '${phpPrefix}_${phpName}_editor_assets' );
|
|
302
|
+
|
|
303
|
+
add_action('wp_enqueue_scripts', function() {
|
|
304
|
+
wp_dequeue_style('wp-fonts-local');
|
|
305
|
+
wp_deregister_style('wp-fonts-local');
|
|
306
|
+
wp_dequeue_style('global-styles');
|
|
307
|
+
wp_deregister_style('global-styles');
|
|
308
|
+
remove_action('wp_footer', 'wp_global_styles_render_svg_filters');
|
|
309
|
+
}, 100);
|
|
310
|
+
|
|
311
|
+
function ${phpPrefix}_${phpName}_editor_assets() {
|
|
312
|
+
$filepath = plugin_dir_path(__FILE__) . 'block.js';
|
|
313
|
+
$version = file_exists($filepath) ? filemtime($filepath) : time();
|
|
314
|
+
|
|
315
|
+
wp_enqueue_script(
|
|
316
|
+
'${blockNameHandle}',
|
|
317
|
+
plugins_url( 'block.js', __FILE__ ),
|
|
318
|
+
array( 'wp-blocks', 'wp-components', 'wp-element' ,'wp-editor'),
|
|
319
|
+
$version
|
|
320
|
+
);
|
|
321
|
+
|
|
322
|
+
wp_localize_script( '${blockNameHandle}', 'vars', array( 'url' => plugin_dir_url( __FILE__ ) ) );
|
|
323
|
+
|
|
324
|
+
${enqueueRemoteStyles}
|
|
325
|
+
|
|
326
|
+
${phpPrefix}_${phpName}_add_custom_editor_styles();
|
|
327
|
+
|
|
328
|
+
wp_dequeue_style('${blockNameHandle}-frontend');
|
|
329
|
+
wp_deregister_style('${blockNameHandle}-frontend');
|
|
330
|
+
|
|
331
|
+
wp_enqueue_script(
|
|
332
|
+
'${blockNameHandle}-remote-loader',
|
|
333
|
+
plugins_url('remote-loader.js', __FILE__),
|
|
334
|
+
array(),
|
|
335
|
+
null,
|
|
336
|
+
true
|
|
337
|
+
);
|
|
338
|
+
|
|
339
|
+
wp_add_inline_script(
|
|
340
|
+
'${blockNameHandle}-remote-loader',
|
|
341
|
+
${JSON.stringify(inlineRemoteLoader)}
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
add_action('enqueue_block_editor_assets', function () {
|
|
346
|
+
wp_dequeue_style('wp-block-library');
|
|
347
|
+
wp_dequeue_style('wp-block-library-theme');
|
|
348
|
+
wp_dequeue_style('wc-block-style');
|
|
349
|
+
wp_dequeue_style('wp-format-library');
|
|
350
|
+
}, 100);
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
add_action('init', function () {
|
|
354
|
+
wp_register_script(
|
|
355
|
+
'${blockNameHandle}-scripts',
|
|
356
|
+
plugins_url('scripts.js', __FILE__),
|
|
357
|
+
array(),
|
|
358
|
+
null,
|
|
359
|
+
true
|
|
360
|
+
);
|
|
361
|
+
|
|
362
|
+
wp_register_style(
|
|
363
|
+
'${blockNameHandle}-frontend',
|
|
364
|
+
plugins_url('style.css', __FILE__)
|
|
365
|
+
);
|
|
366
|
+
|
|
367
|
+
wp_register_script(
|
|
368
|
+
'${blockNameHandle}-remote-loader',
|
|
369
|
+
plugins_url('remote-loader.js', __FILE__),
|
|
370
|
+
array(),
|
|
371
|
+
null,
|
|
372
|
+
true
|
|
373
|
+
);
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
add_action( 'wp_enqueue_scripts', '${phpPrefix}_${phpName}_block_assets', 999 );
|
|
377
|
+
|
|
378
|
+
function ${phpPrefix}_${phpName}_block_assets() {
|
|
379
|
+
global $wp_query;
|
|
380
|
+
|
|
381
|
+
$used = false;
|
|
382
|
+
|
|
383
|
+
if (!empty($wp_query->posts)) {
|
|
384
|
+
foreach ($wp_query->posts as $post) {
|
|
385
|
+
$blocks = parse_blocks($post->post_content);
|
|
386
|
+
|
|
387
|
+
foreach ($blocks as $block) {
|
|
388
|
+
if ($block['blockName'] === '${blockName}') {
|
|
389
|
+
$used = true;
|
|
390
|
+
break 2;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
if ($used) {
|
|
397
|
+
$handle = '${blockNameHandle}';
|
|
398
|
+
|
|
399
|
+
wp_enqueue_style($handle . '-frontend');
|
|
400
|
+
|
|
401
|
+
wp_enqueue_script($handle . '-scripts');
|
|
402
|
+
|
|
403
|
+
wp_localize_script(
|
|
404
|
+
$handle . '-scripts',
|
|
405
|
+
'vars',
|
|
406
|
+
array(
|
|
407
|
+
'postId' => get_queried_object_id(),
|
|
408
|
+
'ajaxUrl' => admin_url('admin-ajax.php')
|
|
409
|
+
)
|
|
410
|
+
);
|
|
411
|
+
|
|
412
|
+
wp_enqueue_script($handle . '-remote-loader');
|
|
413
|
+
|
|
414
|
+
wp_add_inline_script(
|
|
415
|
+
$handle . '-remote-loader',
|
|
416
|
+
${JSON.stringify(inlineRemoteLoader)}
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
`;
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
function transformBlockFile(code) {
|
|
424
|
+
const transformOptions = {
|
|
425
|
+
presets: [[presetReact, { pragma: 'wp.element.createElement' }]],
|
|
426
|
+
filename: 'block.js',
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
return babel.transformSync(code, transformOptions);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
const saveFiles = async (options) => {
|
|
433
|
+
const { cssFiles = [], jsFiles = [], shouldSaveFiles, name, prefix } = options;
|
|
434
|
+
const tailwindRegex = /(class|className)\s*=\s*["'][^"']*\b(items-center|justify-center|gap-\d+|rounded(-[a-z]+)?|text-[a-z]+-\d{3}|bg-[a-z]+-\d{3}|w-(full|screen)|h-(full|screen)|max-w-[\w\[\]-]+|p-\d+|m-\d+)\b[^"']*["']/i;
|
|
435
|
+
const hasTailwind = tailwindRegex.test(htmlContent);
|
|
436
|
+
const hasTailwindCdn = hasTailwindCdnSource(jsFiles);
|
|
437
|
+
|
|
438
|
+
css = hasTailwind && hasTailwindCdn ? '' : `
|
|
439
|
+
*:not(.components-button) {
|
|
440
|
+
all: revert-layer;
|
|
441
|
+
}\n`;
|
|
442
|
+
|
|
443
|
+
css += await parseRequirements(cssFiles, options);
|
|
444
|
+
|
|
445
|
+
for (const style of styles) {
|
|
446
|
+
css += style.content;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
const scopedCssFrontend = scopeCss(css, `.wp-block-${sanitizeAndReplaceLeadingNumbers(replaceUnderscoresSpacesAndUppercaseLetters(prefix))}-${sanitizeAndReplaceLeadingNumbers(replaceUnderscoresSpacesAndUppercaseLetters(name))}`);
|
|
450
|
+
const editorStyleFile = scopeCss(css, `[data-type="${blockName}"]`);
|
|
451
|
+
const scriptFile = js;
|
|
452
|
+
let blockCode = await getBlock(htmlContent, options);
|
|
453
|
+
|
|
454
|
+
blockCode = blockCode
|
|
455
|
+
.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '')
|
|
456
|
+
.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, '')
|
|
457
|
+
.replace(/<link[^>]*?>/gi, '');
|
|
458
|
+
|
|
459
|
+
const indexFile = getPhp(options);
|
|
460
|
+
|
|
461
|
+
const blockFile = transformBlockFile(blockCode).code
|
|
462
|
+
.replace(/name: \"\{field.name\}\"/g, 'name: field.name')
|
|
463
|
+
.replace(/key: \"\{index\}\"/g, 'key: index')
|
|
464
|
+
|
|
465
|
+
if (shouldSaveFiles) {
|
|
466
|
+
saveFile('style.css', scopedCssFrontend, options);
|
|
467
|
+
saveFile('editor.css', editorStyleFile, options);
|
|
468
|
+
saveFile('scripts.js', `${scriptFile}\n\n${emailTemplate}`, options);
|
|
469
|
+
saveFile('index.php', indexFile, options);
|
|
470
|
+
saveFile('block.js', blockFile, options);
|
|
471
|
+
saveFile('remote-loader.js', '', options);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
return {
|
|
475
|
+
'style.css': scopedCssFrontend,
|
|
476
|
+
'editor.css': editorStyleFile,
|
|
477
|
+
'scripts.js': scriptFile,
|
|
478
|
+
'index.php': indexFile,
|
|
479
|
+
'block.js': blockFile,
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
};
|
|
483
|
+
|
|
484
|
+
const logError = (error) => {
|
|
485
|
+
console.error(`[Error] ${error.message}`);
|
|
486
|
+
};
|
|
487
|
+
|
|
488
|
+
const generateRandomVariableName = (prefix = 'content', length = 3) => {
|
|
489
|
+
let suffix = '';
|
|
490
|
+
|
|
491
|
+
for (let i = 0; i < length; i++) {
|
|
492
|
+
suffix += characters.charAt(
|
|
493
|
+
Math.floor(Math.random() * characters.length)
|
|
494
|
+
);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
return `${prefix}${suffix}`;
|
|
498
|
+
};
|
|
499
|
+
|
|
500
|
+
const functionSufix = generateRandomVariableName('func', 6);
|
|
501
|
+
|
|
502
|
+
const setRandomAttributeContent = (randomVariableName, content) => {
|
|
503
|
+
let newContent = content;
|
|
504
|
+
const isArray = Array.isArray(content);
|
|
505
|
+
|
|
506
|
+
if (typeof content === 'string') {
|
|
507
|
+
newContent = content.replace(/(?:<[^>]*>|[^<"]*")|"/g, (match) => {
|
|
508
|
+
if (match === '"') {
|
|
509
|
+
return '"';
|
|
510
|
+
}
|
|
511
|
+
return match;
|
|
512
|
+
})
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
attributes[randomVariableName] = { type: isArray ? 'array' : 'string', default: newContent };
|
|
516
|
+
};
|
|
517
|
+
|
|
518
|
+
const hasAbsoluteKeyword = (str) => {
|
|
519
|
+
return !!(str && str.toLowerCase().includes('absolute'));
|
|
520
|
+
};
|
|
521
|
+
|
|
522
|
+
const getImageTemplate = (image) => {
|
|
523
|
+
const { randomUrlVariable, randomAltVariable, imgClass } = image;
|
|
524
|
+
return `
|
|
525
|
+
<MediaUpload
|
|
526
|
+
onSelect={(media) => {
|
|
527
|
+
setAttributes({
|
|
528
|
+
${randomUrlVariable}: media.url,
|
|
529
|
+
${randomAltVariable}: media.alt
|
|
530
|
+
});
|
|
531
|
+
}}
|
|
532
|
+
type="image"
|
|
533
|
+
render={({ open }) => (
|
|
534
|
+
<div style={{ position: 'relative' }}>
|
|
535
|
+
<img
|
|
536
|
+
src={attributes.${randomUrlVariable}}
|
|
537
|
+
alt={attributes.${randomAltVariable}}
|
|
538
|
+
className="${imgClass}"
|
|
539
|
+
/>
|
|
540
|
+
<div
|
|
541
|
+
onClick={open}
|
|
542
|
+
style={{
|
|
543
|
+
position: 'absolute',
|
|
544
|
+
bottom: '0',
|
|
545
|
+
width: '100%',
|
|
546
|
+
height: '100%',
|
|
547
|
+
zIndex: 10,
|
|
548
|
+
cursor: 'pointer'
|
|
549
|
+
}}
|
|
550
|
+
></div>
|
|
551
|
+
</div>
|
|
552
|
+
)}
|
|
553
|
+
/>
|
|
554
|
+
`;
|
|
555
|
+
};
|
|
556
|
+
|
|
557
|
+
const replaceHtmlImage = (html, image) => {
|
|
558
|
+
const { randomUrlVariable } = image;
|
|
559
|
+
const regex = new RegExp(`<img\\s+[^>]*src=\\{[^}]*${randomUrlVariable}[^}]*\\}[^>]*>`, 'gi');
|
|
560
|
+
return html.replace(regex, getImageTemplate(image));
|
|
561
|
+
};
|
|
562
|
+
|
|
563
|
+
const replaceImageComponents = (html) => {
|
|
564
|
+
images.forEach((image) => {
|
|
565
|
+
html = replaceHtmlImage(html, image);
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
return html;
|
|
569
|
+
};
|
|
570
|
+
const loadHtml = async (options) => {
|
|
571
|
+
const { basePath, htmlContent } = options;
|
|
572
|
+
if (htmlContent) {
|
|
573
|
+
const newHtml = await extractAssets(htmlContent, {
|
|
574
|
+
basePath,
|
|
575
|
+
saveFile: true,
|
|
576
|
+
verbose: false,
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
return cheerio.load(newHtml, {
|
|
580
|
+
xmlMode: true,
|
|
581
|
+
decodeEntities: false,
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
};
|
|
585
|
+
const getImageSource = (imgTag) => {
|
|
586
|
+
return imgTag.attr('src') || '';
|
|
587
|
+
};
|
|
588
|
+
const getImageAlt = (imgTag) => {
|
|
589
|
+
return imgTag.attr('alt') || '';
|
|
590
|
+
};
|
|
591
|
+
const getParentElement = (imgTag) => {
|
|
592
|
+
return imgTag.parent();
|
|
593
|
+
};
|
|
594
|
+
const getImageStyle = (imgTag) => {
|
|
595
|
+
return imgTag.attr('style') || '';
|
|
596
|
+
};
|
|
597
|
+
const getImageClass = (imgTag) => {
|
|
598
|
+
return imgTag.attr('class') || imgTag.attr('className') || '';
|
|
599
|
+
};
|
|
600
|
+
const getPreviousStyle = (parentElement) => {
|
|
601
|
+
return parentElement.attr('style') || '';
|
|
602
|
+
};
|
|
603
|
+
const getParentClass = (parentElement) => {
|
|
604
|
+
return parentElement.attr('class') || parentElement.attr('className') || '';
|
|
605
|
+
};
|
|
606
|
+
const isBackgroundImage = (
|
|
607
|
+
imgStyle,
|
|
608
|
+
imgClass,
|
|
609
|
+
previousStyle,
|
|
610
|
+
previousClass
|
|
611
|
+
) => {
|
|
612
|
+
return (
|
|
613
|
+
hasAbsoluteKeyword(imgStyle) ||
|
|
614
|
+
hasAbsoluteKeyword(imgClass) ||
|
|
615
|
+
hasAbsoluteKeyword(previousStyle) ||
|
|
616
|
+
hasAbsoluteKeyword(previousClass)
|
|
617
|
+
);
|
|
618
|
+
};
|
|
619
|
+
const getImageProperties = (imgTag) => {
|
|
620
|
+
const parentElement = getParentElement(imgTag);
|
|
621
|
+
const imgStyle = getImageStyle(imgTag);
|
|
622
|
+
const imgClass = getImageClass(imgTag);
|
|
623
|
+
const previousStyle = getPreviousStyle(parentElement);
|
|
624
|
+
const previousClass = getParentClass(parentElement);
|
|
625
|
+
const isBackground = isBackgroundImage(
|
|
626
|
+
imgStyle,
|
|
627
|
+
imgClass,
|
|
628
|
+
previousStyle,
|
|
629
|
+
previousClass
|
|
630
|
+
);
|
|
631
|
+
return {
|
|
632
|
+
imgTag,
|
|
633
|
+
imgClass,
|
|
634
|
+
isBackground,
|
|
635
|
+
imgSrc: getImageSource(imgTag),
|
|
636
|
+
imgAlt: getImageAlt(imgTag),
|
|
637
|
+
};
|
|
638
|
+
};
|
|
639
|
+
const setImageAttribute = (properties) => {
|
|
640
|
+
const { imgTag, imgSrc, imgAlt, attribute, type, prefix } = properties;
|
|
641
|
+
const newPrefix = prefix ? replaceUnderscoresSpacesAndUppercaseLetters(prefix) : 'wp';
|
|
642
|
+
const randomVariable = generateRandomVariableName(`${type}${newPrefix}`);
|
|
643
|
+
|
|
644
|
+
attributes[randomVariable] = {
|
|
645
|
+
attribute,
|
|
646
|
+
type: 'string',
|
|
647
|
+
selector: 'img',
|
|
648
|
+
default: attribute === 'alt' ? imgAlt : `var.url+'${imgSrc}'`,
|
|
649
|
+
};
|
|
650
|
+
|
|
651
|
+
imgTag.attr(attribute, `{attributes.${randomVariable}}`);
|
|
652
|
+
|
|
653
|
+
return randomVariable;
|
|
654
|
+
};
|
|
655
|
+
|
|
656
|
+
const processImage = (properties) => {
|
|
657
|
+
const { imgClass, type } = properties;
|
|
658
|
+
|
|
659
|
+
const randomUrlVariable = setImageAttribute({
|
|
660
|
+
...properties,
|
|
661
|
+
attribute: 'src',
|
|
662
|
+
prefix: 'Url',
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
const randomAltVariable = setImageAttribute({
|
|
666
|
+
...properties,
|
|
667
|
+
attribute: 'alt',
|
|
668
|
+
prefix: 'Alt',
|
|
669
|
+
});
|
|
670
|
+
|
|
671
|
+
if (type !== 'background') {
|
|
672
|
+
images.push({ randomUrlVariable, randomAltVariable, imgClass });
|
|
673
|
+
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
createPanel({
|
|
678
|
+
type: 'media',
|
|
679
|
+
title: 'Background Image',
|
|
680
|
+
attributes: [randomUrlVariable, randomAltVariable],
|
|
681
|
+
});
|
|
682
|
+
};
|
|
683
|
+
|
|
684
|
+
const createPanelsForForm = () => {
|
|
685
|
+
const randomFormIdVariable = generateRandomVariableName('form');
|
|
686
|
+
const randomHiddenFieldsAttr = generateRandomVariableName('hiddenFields');
|
|
687
|
+
const randomSendEmailVariable = generateRandomVariableName('send');
|
|
688
|
+
const randomEmailFromVariable = generateRandomVariableName('emailFrom');
|
|
689
|
+
const randomEmailToVariable = generateRandomVariableName('emailTo');
|
|
690
|
+
const randomEmailSubjectVariable = generateRandomVariableName('emailSubj');
|
|
691
|
+
const randomEmailMessageVariable = generateRandomVariableName('emailMsg');
|
|
692
|
+
const randomTestFeedbackAttr = generateRandomVariableName('emailTestMsg');
|
|
693
|
+
|
|
694
|
+
Object.assign(formVars, {
|
|
695
|
+
randomFormIdVariable,
|
|
696
|
+
randomSendEmailVariable,
|
|
697
|
+
randomEmailFromVariable,
|
|
698
|
+
randomEmailToVariable,
|
|
699
|
+
randomEmailSubjectVariable,
|
|
700
|
+
randomEmailMessageVariable,
|
|
701
|
+
randomTestFeedbackAttr,
|
|
702
|
+
randomHiddenFieldsAttr
|
|
703
|
+
});
|
|
704
|
+
|
|
705
|
+
setRandomAttributeContent(randomFormIdVariable, `form-${randomFormIdVariable}`);
|
|
706
|
+
setRandomAttributeContent(randomSendEmailVariable, false);
|
|
707
|
+
setRandomAttributeContent(randomEmailFromVariable, '');
|
|
708
|
+
setRandomAttributeContent(randomEmailToVariable, '');
|
|
709
|
+
setRandomAttributeContent(randomEmailSubjectVariable, 'New Form Submission');
|
|
710
|
+
setRandomAttributeContent(randomEmailMessageVariable, 'Form data:\n{{fields}}');
|
|
711
|
+
setRandomAttributeContent(randomTestFeedbackAttr, '');
|
|
712
|
+
setRandomAttributeContent(randomHiddenFieldsAttr, []);
|
|
713
|
+
|
|
714
|
+
createPanel({
|
|
715
|
+
type: 'formSettings',
|
|
716
|
+
title: 'Form Settings',
|
|
717
|
+
attributes: [randomFormIdVariable, randomSendEmailVariable],
|
|
718
|
+
});
|
|
719
|
+
|
|
720
|
+
createPanel({
|
|
721
|
+
type: 'emailSettings',
|
|
722
|
+
title: 'Email Settings',
|
|
723
|
+
attributes: [
|
|
724
|
+
randomSendEmailVariable,
|
|
725
|
+
randomEmailFromVariable,
|
|
726
|
+
randomEmailToVariable,
|
|
727
|
+
randomEmailSubjectVariable,
|
|
728
|
+
randomEmailMessageVariable,
|
|
729
|
+
randomTestFeedbackAttr,
|
|
730
|
+
randomFormIdVariable
|
|
731
|
+
],
|
|
732
|
+
});
|
|
733
|
+
|
|
734
|
+
createPanel({
|
|
735
|
+
type: 'hiddenFields',
|
|
736
|
+
title: 'Hidden Fields',
|
|
737
|
+
attributes: [randomHiddenFieldsAttr],
|
|
738
|
+
});
|
|
739
|
+
};
|
|
740
|
+
|
|
741
|
+
const getFormVariables = () => ({ ...formVars });
|
|
742
|
+
|
|
743
|
+
const transformFormToDynamicJSX = (htmlContent) => {
|
|
744
|
+
const regex = /<form([\s\S]*?)>([\s\S]*?)<\/form>/gi
|
|
745
|
+
const formExists = regex.test(htmlContent);
|
|
746
|
+
|
|
747
|
+
if (!formExists) {
|
|
748
|
+
return htmlContent;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
|
|
752
|
+
return htmlContent.replace(
|
|
753
|
+
/<form([\s\S]*?)>([\s\S]*?)<\/form>/gi,
|
|
754
|
+
(_match, formAttributes, innerContent) => {
|
|
755
|
+
createPanelsForForm();
|
|
756
|
+
|
|
757
|
+
const {
|
|
758
|
+
randomFormIdVariable,
|
|
759
|
+
randomHiddenFieldsAttr
|
|
760
|
+
} = getFormVariables();
|
|
761
|
+
|
|
762
|
+
return `
|
|
763
|
+
<form
|
|
764
|
+
${formAttributes.trim()}
|
|
765
|
+
id={attributes.${randomFormIdVariable}}
|
|
766
|
+
>
|
|
767
|
+
${innerContent}
|
|
768
|
+
|
|
769
|
+
{ (attributes.${randomHiddenFieldsAttr} || []).map((field, index) => (<input key={index} name={field.name} value={field.value} type="hidden" /> )) }
|
|
770
|
+
</form>
|
|
771
|
+
`;
|
|
772
|
+
}
|
|
773
|
+
);
|
|
774
|
+
};
|
|
775
|
+
|
|
776
|
+
const getFixedHtml = (html) => {
|
|
777
|
+
const onChangeRegex = / onChange="{" \(newtext\)=""\>/gi;
|
|
778
|
+
const closeRichTextRegex = /<\/RichText>/gi;
|
|
779
|
+
const valueBindingRegex = /value="{(.*?)}"/gi;
|
|
780
|
+
const attributeBindingRegex = /"{attributes\.(.*?)}"/gi;
|
|
781
|
+
|
|
782
|
+
const fixOnChange = ' onChange={ (newtext) => ';
|
|
783
|
+
const fixValueBinding = 'value={$1}';
|
|
784
|
+
const fixAttributeBinding = '{attributes.$1}';
|
|
785
|
+
|
|
786
|
+
return html
|
|
787
|
+
.replace(onChangeRegex, fixOnChange)
|
|
788
|
+
.replace(closeRichTextRegex, '')
|
|
789
|
+
.replace(valueBindingRegex, fixValueBinding)
|
|
790
|
+
.replace(attributeBindingRegex, fixAttributeBinding);
|
|
791
|
+
};
|
|
792
|
+
|
|
793
|
+
|
|
794
|
+
const processImages = (imgTag) => {
|
|
795
|
+
const properties = getImageProperties(imgTag);
|
|
796
|
+
const type = properties.isBackground ? 'background' : 'image';
|
|
797
|
+
|
|
798
|
+
processImage({ ...properties, type });
|
|
799
|
+
};
|
|
800
|
+
|
|
801
|
+
|
|
802
|
+
const loopImages = ($) => {
|
|
803
|
+
$('img').each((_index, img) => {
|
|
804
|
+
processImages($(img));
|
|
805
|
+
});
|
|
806
|
+
};
|
|
807
|
+
|
|
808
|
+
const getHtml = ($) => {
|
|
809
|
+
return $.html({ xml: false, decodeEntities: false });
|
|
810
|
+
};
|
|
811
|
+
|
|
812
|
+
const processEditImages = async (options) => {
|
|
813
|
+
const $ = await loadHtml(options);
|
|
814
|
+
loopImages($);
|
|
815
|
+
return replaceImageComponents(getFixedHtml(getHtml($)));
|
|
816
|
+
};
|
|
817
|
+
const getRichTextTemplate = (randomVariable, variableContent) => {
|
|
818
|
+
return `
|
|
819
|
+
><RichText
|
|
820
|
+
tagName="span"
|
|
821
|
+
value={attributes.${randomVariable}}
|
|
822
|
+
default="${variableContent.trim().replace(/"/g, '"')}"
|
|
823
|
+
onChange={ (newtext) => {
|
|
824
|
+
setAttributes({ ${randomVariable}: newtext });
|
|
825
|
+
}}
|
|
826
|
+
/><`;
|
|
827
|
+
};
|
|
828
|
+
|
|
829
|
+
const convertToRichText = (variableContent) => {
|
|
830
|
+
const randomVariable = generateRandomVariableName('content');
|
|
831
|
+
|
|
832
|
+
setRandomAttributeContent(randomVariable, variableContent);
|
|
833
|
+
|
|
834
|
+
return getRichTextTemplate(randomVariable, variableContent);
|
|
835
|
+
};
|
|
836
|
+
|
|
837
|
+
const containsCodeSyntax = (text) => {
|
|
838
|
+
const codePattern = /{|}|\(|\)|=>/;
|
|
839
|
+
return codePattern.test(text.trim());
|
|
840
|
+
};
|
|
841
|
+
|
|
842
|
+
const isEmptyText = (text) => {
|
|
843
|
+
return text.trim() === '';
|
|
844
|
+
};
|
|
845
|
+
|
|
846
|
+
const parseContent = (content) => {
|
|
847
|
+
return content.replace(/>([^<]+)</g, (match, text) => {
|
|
848
|
+
const hasCodeSyntax = containsCodeSyntax(text);
|
|
849
|
+
const isEmpty = isEmptyText(text);
|
|
850
|
+
|
|
851
|
+
if (hasCodeSyntax || isEmpty) {
|
|
852
|
+
return match;
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
return convertToRichText(text);
|
|
856
|
+
});
|
|
857
|
+
};
|
|
858
|
+
|
|
859
|
+
const removeHtmlComments = (html) => {
|
|
860
|
+
return html.replaceAll(/<!--(.*?)-->/gs, '');
|
|
861
|
+
};
|
|
862
|
+
|
|
863
|
+
const wrapWithContainer = (html) => {
|
|
864
|
+
return `<div className="custom-block">${html}</div>`;
|
|
865
|
+
};
|
|
866
|
+
|
|
867
|
+
const getEditJsxContent = async (options) => {
|
|
868
|
+
const dynamicContent = transformFormToDynamicJSX(htmlContent);
|
|
869
|
+
const withoutComments = removeHtmlComments(dynamicContent);
|
|
870
|
+
const wrappedContent = wrapWithContainer(withoutComments);
|
|
871
|
+
|
|
872
|
+
const parsedContent = convert(parseContent(wrappedContent));
|
|
873
|
+
|
|
874
|
+
return await processEditImages({
|
|
875
|
+
...options,
|
|
876
|
+
htmlContent: parsedContent,
|
|
877
|
+
});
|
|
878
|
+
};
|
|
879
|
+
|
|
880
|
+
const hasAttributes = (panel) => {
|
|
881
|
+
return Array.isArray(panel.attributes) && panel.attributes.length > 0;
|
|
882
|
+
};
|
|
883
|
+
|
|
884
|
+
const createPanel = (panel) => {
|
|
885
|
+
if (!hasAttributes(panel)) return;
|
|
886
|
+
|
|
887
|
+
panels.push(panel);
|
|
888
|
+
};
|
|
889
|
+
|
|
890
|
+
const getSvgTemplate = (_match, attributesString, closingTag, variableName) => {
|
|
891
|
+
return `
|
|
892
|
+
<svg
|
|
893
|
+
${attributesString}
|
|
894
|
+
dangerouslySetInnerHTML={{ __html: attributes.${variableName} }}
|
|
895
|
+
>
|
|
896
|
+
${closingTag}
|
|
897
|
+
`;
|
|
898
|
+
};
|
|
899
|
+
|
|
900
|
+
const findSVGMatches = (html) => {
|
|
901
|
+
const svgRegex = /<\s*svg\b((?:[^>'"]|"[^"]*"|'[^']*')*)>(\s*(?:[^<]|<(?!\/svg\s*>))*)(<\/\s*svg\s*>)/gim;
|
|
902
|
+
return [...html.matchAll(svgRegex)];
|
|
903
|
+
};
|
|
904
|
+
|
|
905
|
+
const parseSVGMatch = (match) => {
|
|
906
|
+
const [fullSvg, attributes, content, closingTag] = match;
|
|
907
|
+
const start = match.index;
|
|
908
|
+
const end = start + fullSvg.length;
|
|
909
|
+
|
|
910
|
+
return { fullSvg, attributes, content, closingTag, start, end };
|
|
911
|
+
};
|
|
912
|
+
|
|
913
|
+
const hasContent = (content) => content.trim() !== '';
|
|
914
|
+
|
|
915
|
+
const handleSVGContent = (content) => {
|
|
916
|
+
const variableName = generateRandomVariableName('svg');
|
|
917
|
+
const cleanedContent = content.replaceAll('className', 'class');
|
|
918
|
+
|
|
919
|
+
setRandomAttributeContent(variableName, cleanedContent);
|
|
920
|
+
|
|
921
|
+
createPanel({
|
|
922
|
+
type: 'svg',
|
|
923
|
+
title: 'SVG Markup',
|
|
924
|
+
attributes: [variableName],
|
|
925
|
+
});
|
|
926
|
+
|
|
927
|
+
return variableName;
|
|
928
|
+
};
|
|
929
|
+
|
|
930
|
+
const transformSVG = async (fullSvg, attributes, closingTag, variableName) => {
|
|
931
|
+
const template = getSvgTemplate(fullSvg, attributes, closingTag, variableName);
|
|
932
|
+
return await transform(template, { jsxRuntime: 'classic' });
|
|
933
|
+
};
|
|
934
|
+
|
|
935
|
+
const replaceSVGImages = async (html) => {
|
|
936
|
+
const matches = findSVGMatches(html);
|
|
937
|
+
|
|
938
|
+
let output = '';
|
|
939
|
+
let cursor = 0;
|
|
940
|
+
|
|
941
|
+
for (const match of matches) {
|
|
942
|
+
const { fullSvg, attributes, content, closingTag, start, end } = parseSVGMatch(match);
|
|
943
|
+
|
|
944
|
+
output += html.slice(cursor, start);
|
|
945
|
+
|
|
946
|
+
if (hasContent(content)) {
|
|
947
|
+
const variableName = handleSVGContent(content);
|
|
948
|
+
|
|
949
|
+
const transformed = await transformSVG(
|
|
950
|
+
fullSvg,
|
|
951
|
+
attributes,
|
|
952
|
+
closingTag,
|
|
953
|
+
variableName
|
|
954
|
+
);
|
|
955
|
+
|
|
956
|
+
output += transformed;
|
|
957
|
+
} else {
|
|
958
|
+
output += fullSvg;
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
cursor = end;
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
output += html.slice(cursor);
|
|
965
|
+
|
|
966
|
+
return output;
|
|
967
|
+
};
|
|
968
|
+
|
|
969
|
+
const getSvgPanelTemplate = (panel) => {
|
|
970
|
+
return panel.attributes && attributes[panel.attributes]
|
|
971
|
+
? `
|
|
972
|
+
{ (
|
|
973
|
+
<PanelBody title="${panel.title}">
|
|
974
|
+
<PanelRow>
|
|
975
|
+
<div>
|
|
976
|
+
<TextareaControl
|
|
977
|
+
label="SVG Content"
|
|
978
|
+
help="Enter your SVG content..."
|
|
979
|
+
value={ attributes.${panel.attributes} }
|
|
980
|
+
onChange={ ( value ) => {
|
|
981
|
+
setAttributes({ ${panel.attributes}: value });
|
|
982
|
+
} }
|
|
983
|
+
/>
|
|
984
|
+
</div>
|
|
985
|
+
</PanelRow>
|
|
986
|
+
</PanelBody>
|
|
987
|
+
)}
|
|
988
|
+
`
|
|
989
|
+
: '';
|
|
990
|
+
};
|
|
991
|
+
|
|
992
|
+
const getMediaPanelTemplate = (panel) => {
|
|
993
|
+
const mediaAtts =
|
|
994
|
+
panel.attributes?.[0] && panel.attributes[1]
|
|
995
|
+
? `${panel.attributes[0]}: media.url,
|
|
996
|
+
${panel.attributes[1]}: media.alt`
|
|
997
|
+
: '';
|
|
998
|
+
|
|
999
|
+
return panel.attributes &&
|
|
1000
|
+
panel.attributes[0] &&
|
|
1001
|
+
attributes[panel.attributes[0]]
|
|
1002
|
+
? `
|
|
1003
|
+
<PanelBody title="${panel.title}">
|
|
1004
|
+
<PanelRow>
|
|
1005
|
+
<div>
|
|
1006
|
+
<MediaUpload
|
|
1007
|
+
onSelect={ (media) => {
|
|
1008
|
+
setAttributes({
|
|
1009
|
+
${mediaAtts}
|
|
1010
|
+
});
|
|
1011
|
+
} }
|
|
1012
|
+
type="image"
|
|
1013
|
+
value={ attributes.${panel.attributes?.[0]} }
|
|
1014
|
+
render={({ open }) => (
|
|
1015
|
+
<Button variant="secondary" style={{ marginBottom: "20px" }} onClick={ open }>Select Image</Button>
|
|
1016
|
+
)}
|
|
1017
|
+
/>
|
|
1018
|
+
{attributes.${panel.attributes?.[0]} && (
|
|
1019
|
+
<img src={attributes.${panel.attributes?.[0]}} alt={attributes.${panel.attributes?.[1]}} />
|
|
1020
|
+
)}
|
|
1021
|
+
</div>
|
|
1022
|
+
</PanelRow>
|
|
1023
|
+
</PanelBody>
|
|
1024
|
+
`
|
|
1025
|
+
: '';
|
|
1026
|
+
};
|
|
1027
|
+
|
|
1028
|
+
const getFormSettingsPanelTemplate = (panel) => {
|
|
1029
|
+
const [formIdAttr, sendEmailAttr] = panel.attributes;
|
|
1030
|
+
|
|
1031
|
+
return `
|
|
1032
|
+
<PanelBody title="${panel.title}" initialOpen={true}>
|
|
1033
|
+
<TextControl
|
|
1034
|
+
label="Form ID"
|
|
1035
|
+
disabled="true"
|
|
1036
|
+
value={attributes.${formIdAttr}}
|
|
1037
|
+
onChange={(val) => setAttributes({ ${formIdAttr}: val })}
|
|
1038
|
+
/>
|
|
1039
|
+
<ToggleControl
|
|
1040
|
+
label="Send Email on Submit"
|
|
1041
|
+
checked={attributes.${sendEmailAttr}}
|
|
1042
|
+
onChange={(val) => setAttributes({ ${sendEmailAttr}: val })}
|
|
1043
|
+
/>
|
|
1044
|
+
</PanelBody>
|
|
1045
|
+
`;
|
|
1046
|
+
};
|
|
1047
|
+
|
|
1048
|
+
const getPhpEmailData = (formIdAttr, fromAttr, toAttr, subjectAttr, messageAttr) => {
|
|
1049
|
+
return `
|
|
1050
|
+
add_action('wp_ajax_send_email_${formIdAttr}', function() {
|
|
1051
|
+
//check_ajax_referer('wp_rest');
|
|
1052
|
+
|
|
1053
|
+
$post_id = $_POST['postId'];
|
|
1054
|
+
|
|
1055
|
+
if (!$post_id) {
|
|
1056
|
+
wp_send_json_error('Missing post ID.');
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
$post_data = $_POST;
|
|
1060
|
+
$post = get_post($post_id);
|
|
1061
|
+
|
|
1062
|
+
if (!$post) {
|
|
1063
|
+
wp_send_json_error('Post not found.');
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
$blocks = parse_blocks($post->post_content);
|
|
1067
|
+
|
|
1068
|
+
$target_block = null;
|
|
1069
|
+
|
|
1070
|
+
foreach ($blocks as $block) {
|
|
1071
|
+
if ($block['blockName'] === '${blockName}') {
|
|
1072
|
+
$target_block = $block;
|
|
1073
|
+
break;
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
if (!$target_block) {
|
|
1078
|
+
wp_send_json_error('Block not found.');
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
$attrs = $target_block['attrs'] ?? [];
|
|
1082
|
+
|
|
1083
|
+
$to = sanitize_email(parse_form_placeholders_${functionSufix}($attrs['${toAttr}'] ?? '', $post_data));
|
|
1084
|
+
$from = sanitize_email(parse_form_placeholders_${functionSufix}($attrs['${fromAttr}'] ?? '', $post_data));
|
|
1085
|
+
$subject = sanitize_text_field(parse_form_placeholders_${functionSufix}($attrs['${subjectAttr}'] ?? '', $post_data));
|
|
1086
|
+
$message = wp_kses_post(parse_form_placeholders_${functionSufix}($attrs['${messageAttr}'] ?? '', $post_data));
|
|
1087
|
+
|
|
1088
|
+
if (empty($to) || empty($from)) {
|
|
1089
|
+
wp_send_json_error('Missing email addresses.');
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
$sent = wp_mail($to, $subject, $message, [
|
|
1093
|
+
'Content-Type: text/html; charset=UTF-8',
|
|
1094
|
+
'From: ' . $from
|
|
1095
|
+
]);
|
|
1096
|
+
|
|
1097
|
+
if ($sent) {
|
|
1098
|
+
wp_send_json_success('Email sent');
|
|
1099
|
+
} else {
|
|
1100
|
+
error_log('Failed to send. To: ' . $to . ' | From: ' . $from);
|
|
1101
|
+
wp_send_json_error('Failed to send email.');
|
|
1102
|
+
}
|
|
1103
|
+
});
|
|
1104
|
+
`;
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
const getEmailSettingsPanelTemplate = (panel) => {
|
|
1108
|
+
const [
|
|
1109
|
+
sendEmailAttr,
|
|
1110
|
+
fromAttr,
|
|
1111
|
+
toAttr,
|
|
1112
|
+
subjectAttr,
|
|
1113
|
+
messageAttr,
|
|
1114
|
+
testFeedbackAttr,
|
|
1115
|
+
formIdAttr,
|
|
1116
|
+
] = panel.attributes;
|
|
1117
|
+
|
|
1118
|
+
emailTemplate += sendEmailAttr ? getEmailSaveTemplate(formIdAttr) : '';
|
|
1119
|
+
phpEmailData += getPhpEmailData(formIdAttr, fromAttr, toAttr, subjectAttr, messageAttr);
|
|
1120
|
+
|
|
1121
|
+
return `
|
|
1122
|
+
{ attributes.${sendEmailAttr} && (
|
|
1123
|
+
<PanelBody title="${panel.title}" initialOpen={true}>
|
|
1124
|
+
<TextControl
|
|
1125
|
+
label="From"
|
|
1126
|
+
value={attributes.${fromAttr}}
|
|
1127
|
+
onChange={(val) => setAttributes({ ${fromAttr}: val })}
|
|
1128
|
+
/>
|
|
1129
|
+
<TextControl
|
|
1130
|
+
label="To"
|
|
1131
|
+
value={attributes.${toAttr}}
|
|
1132
|
+
onChange={(val) => setAttributes({ ${toAttr}: val })}
|
|
1133
|
+
/>
|
|
1134
|
+
<TextControl
|
|
1135
|
+
label="Subject"
|
|
1136
|
+
value={attributes.${subjectAttr}}
|
|
1137
|
+
onChange={(val) => setAttributes({ ${subjectAttr}: val })}
|
|
1138
|
+
/>
|
|
1139
|
+
<TextareaControl
|
|
1140
|
+
label="Message Template (HTML Supported)"
|
|
1141
|
+
help="Use {{fieldName}} to insert field values."
|
|
1142
|
+
value={attributes.${messageAttr}}
|
|
1143
|
+
onChange={(val) => setAttributes({ ${messageAttr}: val })}
|
|
1144
|
+
/>
|
|
1145
|
+
|
|
1146
|
+
<Button
|
|
1147
|
+
variant="primary"
|
|
1148
|
+
onClick={() => {
|
|
1149
|
+
const form = document.getElementById('form-${formIdAttr}');
|
|
1150
|
+
const inputs = form.querySelectorAll('input, select, textarea');
|
|
1151
|
+
const body = new URLSearchParams();
|
|
1152
|
+
|
|
1153
|
+
inputs.forEach(input => {
|
|
1154
|
+
if (input.name && !input.disabled) {
|
|
1155
|
+
body.append(input.name, input.value);
|
|
1156
|
+
}
|
|
1157
|
+
});
|
|
1158
|
+
|
|
1159
|
+
body.append('action', 'send_test_email_${functionSufix}');
|
|
1160
|
+
body.append('from', attributes?.${fromAttr} || '');
|
|
1161
|
+
body.append('to', attributes?.${toAttr} || '');
|
|
1162
|
+
body.append('subject', attributes?.${subjectAttr} || '');
|
|
1163
|
+
body.append('message', attributes?.${messageAttr} || '');
|
|
1164
|
+
|
|
1165
|
+
fetch(vars.ajaxUrl, {
|
|
1166
|
+
method: 'POST',
|
|
1167
|
+
body
|
|
1168
|
+
})
|
|
1169
|
+
.then(res => res.json())
|
|
1170
|
+
.then(data => {
|
|
1171
|
+
setAttributes({ ${testFeedbackAttr}: data.success ? 'Test Email Sent!' : data.error });
|
|
1172
|
+
})
|
|
1173
|
+
.catch(() => {
|
|
1174
|
+
setAttributes({ ${testFeedbackAttr}: 'Failed to send test email.' });
|
|
1175
|
+
});
|
|
1176
|
+
}}
|
|
1177
|
+
>
|
|
1178
|
+
Send Test Email
|
|
1179
|
+
</Button>
|
|
1180
|
+
|
|
1181
|
+
{attributes.${testFeedbackAttr} && (
|
|
1182
|
+
<Notice status="info" isDismissible={false}>
|
|
1183
|
+
{attributes.${testFeedbackAttr}}
|
|
1184
|
+
</Notice>
|
|
1185
|
+
)}
|
|
1186
|
+
</PanelBody>
|
|
1187
|
+
)}
|
|
1188
|
+
`;
|
|
1189
|
+
};
|
|
1190
|
+
|
|
1191
|
+
const getEmailSaveTemplate = (formIdAttr) => {
|
|
1192
|
+
return `
|
|
1193
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
1194
|
+
const form = document.getElementById('form-${formIdAttr}');
|
|
1195
|
+
|
|
1196
|
+
form.addEventListener('submit', function(event) {
|
|
1197
|
+
event.preventDefault();
|
|
1198
|
+
|
|
1199
|
+
const inputs = form.querySelectorAll('input, select, textarea');
|
|
1200
|
+
const body = new URLSearchParams();
|
|
1201
|
+
|
|
1202
|
+
inputs.forEach(input => {
|
|
1203
|
+
if (input.name && !input.disabled) {
|
|
1204
|
+
body.append(input.name, input.value);
|
|
1205
|
+
}
|
|
1206
|
+
});
|
|
1207
|
+
|
|
1208
|
+
body.append('action', 'send_email_${formIdAttr}');
|
|
1209
|
+
body.append('postId', vars.postId);
|
|
1210
|
+
|
|
1211
|
+
fetch(vars.ajaxUrl, {
|
|
1212
|
+
method: 'POST',
|
|
1213
|
+
body
|
|
1214
|
+
})
|
|
1215
|
+
.then(res => res.json())
|
|
1216
|
+
.then(data => {
|
|
1217
|
+
console.log(data.success ? 'Test Email Sent!' : data.error);
|
|
1218
|
+
})
|
|
1219
|
+
.catch(() => {
|
|
1220
|
+
console.log('Failed to send test email.');
|
|
1221
|
+
});
|
|
1222
|
+
|
|
1223
|
+
form.reset()
|
|
1224
|
+
});
|
|
1225
|
+
});
|
|
1226
|
+
`;
|
|
1227
|
+
};
|
|
1228
|
+
|
|
1229
|
+
const getHiddenFieldsPanelTemplate = (panel) => {
|
|
1230
|
+
const [hiddenFieldsAttr] = panel.attributes;
|
|
1231
|
+
|
|
1232
|
+
return `
|
|
1233
|
+
<PanelBody title="${panel.title}" initialOpen={false}>
|
|
1234
|
+
{ (attributes.${hiddenFieldsAttr} || []).map((field, index) => (
|
|
1235
|
+
<div key={index} style={{ marginBottom: '10px', borderBottom: '1px solid #ddd', paddingBottom: '10px' }}>
|
|
1236
|
+
<TextControl
|
|
1237
|
+
label="Name"
|
|
1238
|
+
value={field.name}
|
|
1239
|
+
onChange={(val) => {
|
|
1240
|
+
const newFields = [...attributes.${hiddenFieldsAttr}];
|
|
1241
|
+
newFields[index].name = val;
|
|
1242
|
+
setAttributes({ ${hiddenFieldsAttr}: newFields });
|
|
1243
|
+
}}
|
|
1244
|
+
/>
|
|
1245
|
+
<TextControl
|
|
1246
|
+
label="Value"
|
|
1247
|
+
value={field.value}
|
|
1248
|
+
onChange={(val) => {
|
|
1249
|
+
const newFields = [...attributes.${hiddenFieldsAttr}];
|
|
1250
|
+
newFields[index].value = val;
|
|
1251
|
+
setAttributes({ ${hiddenFieldsAttr}: newFields });
|
|
1252
|
+
}}
|
|
1253
|
+
/>
|
|
1254
|
+
<Button
|
|
1255
|
+
variant="secondary"
|
|
1256
|
+
onClick={() => {
|
|
1257
|
+
const newFields = [...attributes.${hiddenFieldsAttr}];
|
|
1258
|
+
newFields.splice(index, 1);
|
|
1259
|
+
setAttributes({ ${hiddenFieldsAttr}: newFields });
|
|
1260
|
+
}}
|
|
1261
|
+
>
|
|
1262
|
+
Remove
|
|
1263
|
+
</Button>
|
|
1264
|
+
</div>
|
|
1265
|
+
))}
|
|
1266
|
+
|
|
1267
|
+
<Button
|
|
1268
|
+
variant="primary"
|
|
1269
|
+
onClick={() => {
|
|
1270
|
+
const newFields = [...(attributes.${hiddenFieldsAttr} || [])];
|
|
1271
|
+
newFields.push({ name: '', value: '' });
|
|
1272
|
+
setAttributes({ ${hiddenFieldsAttr}: newFields });
|
|
1273
|
+
}}
|
|
1274
|
+
>
|
|
1275
|
+
Add Hidden Field
|
|
1276
|
+
</Button>
|
|
1277
|
+
</PanelBody>
|
|
1278
|
+
`;
|
|
1279
|
+
};
|
|
1280
|
+
|
|
1281
|
+
|
|
1282
|
+
const findAndGetPanelTemplate = (panel) => {
|
|
1283
|
+
switch (panel.type) {
|
|
1284
|
+
case 'svg':
|
|
1285
|
+
return getSvgPanelTemplate(panel);
|
|
1286
|
+
case 'media':
|
|
1287
|
+
return getMediaPanelTemplate(panel);
|
|
1288
|
+
case 'formSettings':
|
|
1289
|
+
return getFormSettingsPanelTemplate(panel);
|
|
1290
|
+
case 'emailSettings':
|
|
1291
|
+
return getEmailSettingsPanelTemplate(panel);
|
|
1292
|
+
case 'hiddenFields':
|
|
1293
|
+
return getHiddenFieldsPanelTemplate(panel);
|
|
1294
|
+
default:
|
|
1295
|
+
return '';
|
|
1296
|
+
}
|
|
1297
|
+
};
|
|
1298
|
+
|
|
1299
|
+
const getPanelsTemplate = () => {
|
|
1300
|
+
return panels
|
|
1301
|
+
.map((panel) => {
|
|
1302
|
+
return findAndGetPanelTemplate(panel);
|
|
1303
|
+
})
|
|
1304
|
+
.join('\n');
|
|
1305
|
+
};
|
|
1306
|
+
|
|
1307
|
+
const createPanels = () => {
|
|
1308
|
+
return `
|
|
1309
|
+
<Panel>
|
|
1310
|
+
${getPanelsTemplate()}
|
|
1311
|
+
</Panel>`;
|
|
1312
|
+
};
|
|
1313
|
+
|
|
1314
|
+
const buildSaveContent = (editContent) => {
|
|
1315
|
+
return editContent.replace(
|
|
1316
|
+
/<RichText((.|\n)*?)value=\{(.*?)\}((.|\n)*?)\/>/gi,
|
|
1317
|
+
'<RichText.Content value={$3} />'
|
|
1318
|
+
)
|
|
1319
|
+
.replace(/className=/gi, 'class=')
|
|
1320
|
+
.replace(
|
|
1321
|
+
/<MediaUpload\b[^>]*>([\s\S]*?(<img\b[^>]*>*\/>)[\s\S]*?)\/>/g,
|
|
1322
|
+
(_match, _attributes, img) => {
|
|
1323
|
+
return img.replace(/onClick={[^}]+}\s*/, '');
|
|
1324
|
+
}
|
|
1325
|
+
);
|
|
1326
|
+
};
|
|
1327
|
+
|
|
1328
|
+
const removeHref = (match) => {
|
|
1329
|
+
return match.replace(/href="(.*?)"/, '');
|
|
1330
|
+
};
|
|
1331
|
+
|
|
1332
|
+
const replaceRichText = (match, group1, _group2, group3) => {
|
|
1333
|
+
return removeHref(match)
|
|
1334
|
+
.replace(group1, '<span')
|
|
1335
|
+
.replace(group3, '</span>');
|
|
1336
|
+
};
|
|
1337
|
+
|
|
1338
|
+
const processLinks = (options) => {
|
|
1339
|
+
let { htmlContent } = options;
|
|
1340
|
+
|
|
1341
|
+
htmlContent = htmlContent
|
|
1342
|
+
? htmlContent.replace(
|
|
1343
|
+
/(<a)[^>]*>([\s\S]*?)(<\/a>)/gim,
|
|
1344
|
+
replaceRichText
|
|
1345
|
+
)
|
|
1346
|
+
: undefined;
|
|
1347
|
+
|
|
1348
|
+
htmlContent = unwrapAnchor(htmlContent);
|
|
1349
|
+
|
|
1350
|
+
return {
|
|
1351
|
+
...options,
|
|
1352
|
+
htmlContent,
|
|
1353
|
+
};
|
|
1354
|
+
};
|
|
1355
|
+
|
|
1356
|
+
const mergeAttributes = (attrString) => {
|
|
1357
|
+
const attrRegex = /(\S+)=["'](.*?)["']/g;
|
|
1358
|
+
const attrs = {};
|
|
1359
|
+
let match;
|
|
1360
|
+
|
|
1361
|
+
while ((match = attrRegex.exec(attrString)) !== null) {
|
|
1362
|
+
attrs[match[1]] = match[2];
|
|
1363
|
+
}
|
|
1364
|
+
return attrs;
|
|
1365
|
+
};
|
|
1366
|
+
|
|
1367
|
+
const unwrapAnchor = (htmlContent) => {
|
|
1368
|
+
|
|
1369
|
+
return htmlContent.replace(
|
|
1370
|
+
/<span([^>]*)>\s*<a([^>]*)>(.*?)<\/a>\s*<\/span>/gi,
|
|
1371
|
+
(_, spanAttrs, anchorAttrs, content) => {
|
|
1372
|
+
const spanAttributes = mergeAttributes(spanAttrs);
|
|
1373
|
+
const anchorAttributes = mergeAttributes(anchorAttrs);
|
|
1374
|
+
const allAttributes = { ...spanAttributes, ...anchorAttributes };
|
|
1375
|
+
|
|
1376
|
+
const attrString = Object.entries(allAttributes)
|
|
1377
|
+
.map(([key, value]) => `${key}="${value}"`)
|
|
1378
|
+
.join(' ');
|
|
1379
|
+
|
|
1380
|
+
return `<a ${attrString}>${content}</a>`;
|
|
1381
|
+
}
|
|
1382
|
+
);
|
|
1383
|
+
};
|
|
1384
|
+
|
|
1385
|
+
|
|
1386
|
+
const getComponentAttributes = () => {
|
|
1387
|
+
return JSON.stringify(attributes, null, 2);
|
|
1388
|
+
};
|
|
1389
|
+
|
|
1390
|
+
const getEdit = async (options) => {
|
|
1391
|
+
let { htmlContent } = options;
|
|
1392
|
+
|
|
1393
|
+
if (!htmlContent) {
|
|
1394
|
+
return '';
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
const processedOptions = processLinks(options);
|
|
1398
|
+
const jsxContent = await getEditJsxContent(processedOptions);
|
|
1399
|
+
|
|
1400
|
+
const replacedContent = jsxContent
|
|
1401
|
+
.replace(/<article\b([^>]*)>/gi, '<div$1>')
|
|
1402
|
+
.replace(/<\/article>/gi, '</div>')
|
|
1403
|
+
.replaceAll('../', '')
|
|
1404
|
+
.replace(/style="([^"]+)"/g, (_, styleString) => {
|
|
1405
|
+
const styleObj = parseInlineStyle(styleString);
|
|
1406
|
+
return `style={${styleObj}}`;
|
|
1407
|
+
});
|
|
1408
|
+
|
|
1409
|
+
return await replaceSVGImages(replacedContent);
|
|
1410
|
+
};
|
|
1411
|
+
|
|
1412
|
+
|
|
1413
|
+
const parseBlockAttributes = () => {
|
|
1414
|
+
const attributes = getComponentAttributes();
|
|
1415
|
+
const jsonString = JSON.stringify(attributes, null, 2);
|
|
1416
|
+
|
|
1417
|
+
return jsonString
|
|
1418
|
+
.replace(/"var.url\+\'(.*?)\'(.*?)"/g, "vars.url+'$1'$2")
|
|
1419
|
+
.replace(/var(.*?).url\+'(http.*?)'/g, 'http$2');
|
|
1420
|
+
};
|
|
1421
|
+
|
|
1422
|
+
const fixDangerouslySetInnerHTML = (edit) => {
|
|
1423
|
+
const pattern = /dangerouslySetInnerHTML="{" {="" __html:="" (.*?)="" }}=""/gm;
|
|
1424
|
+
const replacement = 'dangerouslySetInnerHTML={{ __html: $1 }}';
|
|
1425
|
+
|
|
1426
|
+
return edit.replace(pattern, replacement);
|
|
1427
|
+
};
|
|
1428
|
+
|
|
1429
|
+
const getBlock = async (htmlContent, settings) => {
|
|
1430
|
+
let {
|
|
1431
|
+
name,
|
|
1432
|
+
category,
|
|
1433
|
+
generateIconPreview,
|
|
1434
|
+
basePath,
|
|
1435
|
+
cssFiles = [],
|
|
1436
|
+
jsFiles = [],
|
|
1437
|
+
} = settings;
|
|
1438
|
+
|
|
1439
|
+
let iconPreview = "'shield'";
|
|
1440
|
+
let edit = await getEdit(settings);
|
|
1441
|
+
|
|
1442
|
+
edit = fixDangerouslySetInnerHTML(edit);
|
|
1443
|
+
|
|
1444
|
+
const save = buildSaveContent(edit);
|
|
1445
|
+
const blockPanels = createPanels();
|
|
1446
|
+
const blockAttributes = parseBlockAttributes();
|
|
1447
|
+
|
|
1448
|
+
if (generateIconPreview) {
|
|
1449
|
+
try {
|
|
1450
|
+
await icon(htmlContent, { basePath, cssFiles, jsFiles });
|
|
1451
|
+
iconPreview = `(<img src="data:image/jpeg;base64,${await imageToBase64(
|
|
1452
|
+
path.join(basePath, 'preview.jpeg')
|
|
1453
|
+
)}" />)`;
|
|
1454
|
+
} catch (error) {
|
|
1455
|
+
console.log(`There was an error generating preview. ${error.message}`);
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
const output = `
|
|
1460
|
+
(function () {
|
|
1461
|
+
${imports}
|
|
1462
|
+
|
|
1463
|
+
registerBlockType('${blockName}', {
|
|
1464
|
+
title: '${name}',
|
|
1465
|
+
icon: ${iconPreview},
|
|
1466
|
+
category: '${category}',
|
|
1467
|
+
attributes: ${blockAttributes},
|
|
1468
|
+
edit(props) {
|
|
1469
|
+
const { attributes, setAttributes } = props;
|
|
1470
|
+
|
|
1471
|
+
return (
|
|
1472
|
+
<div>
|
|
1473
|
+
<InspectorControls>
|
|
1474
|
+
${blockPanels}
|
|
1475
|
+
</InspectorControls>
|
|
1476
|
+
|
|
1477
|
+
${edit}
|
|
1478
|
+
</div>
|
|
1479
|
+
);
|
|
1480
|
+
},
|
|
1481
|
+
save(props) {
|
|
1482
|
+
const { attributes } = props;
|
|
1483
|
+
|
|
1484
|
+
return (
|
|
1485
|
+
${save}
|
|
1486
|
+
);
|
|
1487
|
+
},
|
|
1488
|
+
});
|
|
1489
|
+
})();`;
|
|
1490
|
+
|
|
1491
|
+
if (generateIconPreview) {
|
|
1492
|
+
return output.replace(/icon: \s * (')([^']*)(')/, 'icon: $2');
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
return output;
|
|
1496
|
+
};
|
|
1497
|
+
|
|
1498
|
+
const setupVariables = async (htmlContent, options) => {
|
|
1499
|
+
const styleRegex = /<style[^>]*>([\s\S]*?)<\/style>/gi;
|
|
1500
|
+
const linkRegex = /<link\s+[^>]*href=["']([^"']+)["'][^>]*>/gi;
|
|
1501
|
+
|
|
1502
|
+
let match;
|
|
1503
|
+
|
|
1504
|
+
htmlContent = htmlContent.replace(styleRegex, (_fullMatch, cssContent) => {
|
|
1505
|
+
styles.push({ type: 'inline', content: cssContent.trim() });
|
|
1506
|
+
return '';
|
|
1507
|
+
});
|
|
1508
|
+
|
|
1509
|
+
const fetchCssPromises = [];
|
|
1510
|
+
while ((match = linkRegex.exec(htmlContent)) !== null) {
|
|
1511
|
+
const url = match[1];
|
|
1512
|
+
const fetchCssPromise = fetch(url)
|
|
1513
|
+
.then((response) => response.text())
|
|
1514
|
+
.then((css) => styles.push({ type: 'external', content: css }))
|
|
1515
|
+
.catch(() => console.warn(`Failed to fetch: ${url}`));
|
|
1516
|
+
fetchCssPromises.push(fetchCssPromise);
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
htmlContent = htmlContent.replace(linkRegex, '');
|
|
1520
|
+
|
|
1521
|
+
await Promise.all(fetchCssPromises);
|
|
1522
|
+
|
|
1523
|
+
css += styles.map((style) => {
|
|
1524
|
+
return `${style.content}`;
|
|
1525
|
+
}).join('\n');
|
|
1526
|
+
|
|
1527
|
+
|
|
1528
|
+
const scriptRegex = /<script[^>]*>([\s\S]*?)<\/script>/gi;
|
|
1529
|
+
const scriptSrcRegex =
|
|
1530
|
+
/<script\s+[^>]*src=["']([^"']+)["'][^>]*>\s*<\/script>/gi;
|
|
1531
|
+
|
|
1532
|
+
let jsMatch;
|
|
1533
|
+
|
|
1534
|
+
htmlContent = htmlContent.replace(scriptRegex, (_fullMatch, jsContent) => {
|
|
1535
|
+
if (jsContent.trim()) {
|
|
1536
|
+
scripts.push({ type: 'inline', content: jsContent });
|
|
1537
|
+
}
|
|
1538
|
+
return '';
|
|
1539
|
+
});
|
|
1540
|
+
|
|
1541
|
+
const fetchJsPromises = [];
|
|
1542
|
+
|
|
1543
|
+
while ((jsMatch = scriptSrcRegex.exec(htmlContent)) !== null) {
|
|
1544
|
+
const url = jsMatch[1];
|
|
1545
|
+
const fetchJsPromise = fetch(url)
|
|
1546
|
+
.then((response) => response.text())
|
|
1547
|
+
.then((js) => scripts.push({ type: 'external', content: js }))
|
|
1548
|
+
.catch(() => console.warn(`Failed to fetch script: ${url}`));
|
|
1549
|
+
fetchJsPromises.push(fetchJsPromise);
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
htmlContent = htmlContent.replace(scriptSrcRegex, '');
|
|
1553
|
+
|
|
1554
|
+
await Promise.all(fetchJsPromises);
|
|
1555
|
+
|
|
1556
|
+
js += scripts.map((script) => script.content).join('\n');
|
|
1557
|
+
|
|
1558
|
+
let {
|
|
1559
|
+
basePath = process.cwd(),
|
|
1560
|
+
cssFiles = [],
|
|
1561
|
+
jsFiles = [],
|
|
1562
|
+
name = 'My block',
|
|
1563
|
+
} = options;
|
|
1564
|
+
|
|
1565
|
+
const newDir = path.join(basePath, replaceUnderscoresSpacesAndUppercaseLetters(name));
|
|
1566
|
+
|
|
1567
|
+
try {
|
|
1568
|
+
fs.mkdirSync(newDir, { recursive: true });
|
|
1569
|
+
|
|
1570
|
+
return {
|
|
1571
|
+
...options,
|
|
1572
|
+
jsFiles,
|
|
1573
|
+
cssFiles,
|
|
1574
|
+
htmlContent,
|
|
1575
|
+
basePath: newDir,
|
|
1576
|
+
};
|
|
1577
|
+
} catch (error) {
|
|
1578
|
+
logError(error);
|
|
1579
|
+
}
|
|
1580
|
+
};
|
|
1581
|
+
|
|
1582
|
+
if (source) {
|
|
1583
|
+
htmlContent = replaceRelativeUrlsInHtml(htmlContent, source);
|
|
1584
|
+
htmlContent = replaceRelativeUrlsInCssWithBase(htmlContent, source);
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
return saveFiles(await setupVariables(htmlContent, options));
|
|
1588
|
+
};
|
|
1589
|
+
export default block;
|