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.
- package/.env +1 -0
- package/.env.example +3 -0
- package/.eslintrc.json +35 -0
- package/.github/workflows/build.yml +26 -0
- package/.github/workflows/coverage.yml +26 -0
- package/.nyc_output/1f0406b8-bb70-495d-8f8a-521fdd81b500.json +1 -0
- package/.nyc_output/6390956f-4f8a-4adb-9256-4a1c7e34a52d.json +1 -0
- package/.nyc_output/processinfo/1f0406b8-bb70-495d-8f8a-521fdd81b500.json +1 -0
- package/.nyc_output/processinfo/6390956f-4f8a-4adb-9256-4a1c7e34a52d.json +1 -0
- package/.nyc_output/processinfo/index.json +1 -0
- package/@types.d.ts +3 -0
- package/coverage/coverage-final.json +4 -0
- package/coverage/lcov-report/base.css +224 -0
- package/coverage/lcov-report/block-navigation.js +87 -0
- package/coverage/lcov-report/prettify.css +1 -0
- package/coverage/lcov-report/prettify.js +2 -0
- package/coverage/lcov-report/sorter.js +210 -0
- package/coverage/lcov.info +198 -0
- package/coverage-demo.test.ts +8 -0
- package/dist/coverage-demo.test.js +10 -0
- package/dist/coverage-demo.test.js.map +1 -0
- package/dist/globals.js +24 -0
- package/dist/globals.js.map +1 -0
- package/dist/index.js +36 -0
- package/dist/index.js.map +1 -0
- package/dist/index.test.js +166 -0
- package/dist/index.test.js.map +1 -0
- package/dist/package.json +130 -0
- package/dist/snapapi-screenshot.test.js +44 -0
- package/dist/snapapi-screenshot.test.js.map +1 -0
- package/dist/src/coverage-demo.js +7 -0
- package/dist/src/coverage-demo.js.map +1 -0
- package/dist/src/utils-extra.test.js +137 -0
- package/dist/src/utils-extra.test.js.map +1 -0
- package/dist/src/utils.test.js +65 -0
- package/dist/src/utils.test.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/utils.js +61 -0
- package/dist/utils.js.map +1 -0
- package/index.js +87 -53
- package/index.test.ts +145 -0
- package/index.ts +47 -1527
- package/package.json +85 -14
- package/readme.md +39 -19
- package/snapapi-screenshot.test.ts +46 -0
- package/src/coverage-demo.ts +3 -0
- package/src/utils-extra.test.ts +108 -0
- package/src/utils.test.ts +36 -0
- package/temp-block-test.js +19 -0
- package/tsconfig.json +9 -3
- 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
|
|
8
|
-
import
|
|
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
|
|
19
|
-
const { version } = require('./package.json');
|
|
37
|
+
const { version } = pkg;
|
|
20
38
|
|
|
21
39
|
const block = async (
|
|
22
|
-
htmlContent,
|
|
23
|
-
options
|
|
24
|
-
name
|
|
25
|
-
prefix
|
|
26
|
-
category
|
|
27
|
-
basePath
|
|
28
|
-
shouldSaveFiles
|
|
29
|
-
generateIconPreview
|
|
30
|
-
jsFiles
|
|
31
|
-
cssFiles
|
|
32
|
-
source
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
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, '"')}"
|
|
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;
|