html-to-gutenberg 4.2.7 → 4.2.9

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