html-to-gutenberg 4.2.4 → 4.2.5
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/index.js +287 -344
- package/package.json +3 -3
package/index.js
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import presetReact from '@babel/preset-react';
|
|
2
|
-
import { transform } from '@svgr/core';
|
|
3
2
|
import * as babel from '@babel/core';
|
|
4
3
|
import * as cheerio from 'cheerio';
|
|
5
4
|
import scopeCss from 'css-scoping';
|
|
6
5
|
import extractAssets from 'fetch-page-assets';
|
|
7
6
|
import fs from 'fs';
|
|
8
7
|
import icon from 'html-screenshots';
|
|
9
|
-
import imageToBase64 from 'image-to-base64';
|
|
10
8
|
import { createRequire } from 'module';
|
|
11
9
|
import convert from 'node-html-to-jsx';
|
|
12
10
|
import path from 'path';
|
|
@@ -52,125 +50,93 @@ const block = async (
|
|
|
52
50
|
|
|
53
51
|
return jsFiles.some(url => tailwindCdnRegex.test(url));
|
|
54
52
|
}
|
|
55
|
-
|
|
56
|
-
function
|
|
57
|
-
if (!
|
|
58
|
-
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
.filter(Boolean)
|
|
63
|
-
.map(rule => {
|
|
64
|
-
const [property, value] = rule.split(':').map(part => part.trim());
|
|
65
|
-
|
|
66
|
-
if (!property || !value) return null;
|
|
67
|
-
|
|
68
|
-
const camelCasedProperty = property.replace(/-([a-z])/g, (_, char) => char.toUpperCase());
|
|
69
|
-
|
|
70
|
-
return [camelCasedProperty, value];
|
|
71
|
-
})
|
|
72
|
-
.filter(Boolean);
|
|
73
|
-
|
|
74
|
-
const styleObject = Object.fromEntries(styleEntries);
|
|
75
|
-
|
|
76
|
-
return JSON.stringify(styleObject).replace(/"([^"]+)":/g, '$1:');
|
|
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}`);
|
|
77
60
|
}
|
|
78
61
|
|
|
79
|
-
function sanitizeAndReplaceLeadingNumbers(
|
|
62
|
+
function sanitizeAndReplaceLeadingNumbers(str) {
|
|
80
63
|
const numberWords = ['zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine'];
|
|
81
|
-
let
|
|
64
|
+
let firstNumberReplaced = false;
|
|
82
65
|
|
|
83
|
-
return
|
|
66
|
+
return str
|
|
84
67
|
.toLowerCase()
|
|
85
68
|
.replace(/[\s\-_]/g, '')
|
|
86
69
|
.replace(/\d/g, (digit) => {
|
|
87
|
-
if (
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
70
|
+
if (!firstNumberReplaced) {
|
|
71
|
+
firstNumberReplaced = true;
|
|
72
|
+
|
|
73
|
+
return numberWords[parseInt(digit)] + digit;
|
|
74
|
+
}
|
|
75
|
+
return digit;
|
|
91
76
|
})
|
|
92
77
|
.replace(/^[^a-z]+/, '');
|
|
93
|
-
}
|
|
78
|
+
}
|
|
94
79
|
|
|
95
|
-
const replaceUnderscoresSpacesAndUppercaseLetters = (
|
|
96
|
-
|
|
97
|
-
return input.replace(nonWordOrUnderscore, '-').toLowerCase();
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
const buildFilePath = (basePath, fileName) => {
|
|
101
|
-
return path.join(basePath, fileName);
|
|
80
|
+
const replaceUnderscoresSpacesAndUppercaseLetters = (name = '') => {
|
|
81
|
+
return name.replace(new RegExp(/\W|_/, 'g'), '-').toLowerCase();
|
|
102
82
|
};
|
|
103
|
-
|
|
104
|
-
const writeFile = (filePath, data) => {
|
|
105
|
-
fs.writeFileSync(filePath, data);
|
|
106
|
-
};
|
|
107
83
|
|
|
108
84
|
const saveFile = (fileName, contents, options) => {
|
|
109
85
|
try {
|
|
110
|
-
const filePath =
|
|
111
|
-
|
|
86
|
+
const filePath = path.join(options.basePath, fileName);
|
|
87
|
+
|
|
88
|
+
fs.writeFileSync(filePath, contents);
|
|
89
|
+
|
|
112
90
|
return contents;
|
|
113
91
|
} catch (error) {
|
|
114
92
|
logError(error);
|
|
115
93
|
}
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
function
|
|
119
|
-
const
|
|
120
|
-
'src',
|
|
121
|
-
'href',
|
|
122
|
-
'action',
|
|
123
|
-
'srcset',
|
|
124
|
-
'poster',
|
|
125
|
-
'data',
|
|
126
|
-
'formaction'
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
function replaceRelativeUrls(html, replacer) {
|
|
97
|
+
const urlAttributes = [
|
|
98
|
+
'src', 'href', 'action', 'srcset', 'poster', 'data', 'formaction'
|
|
127
99
|
];
|
|
128
100
|
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
return new RegExp(
|
|
132
|
-
`\\b(${attributes.join('|')}|data-[a-zA-Z0-9_-]+)\\s*=\\s*(['"])(?!${protocolsToIgnore})([^'"]+)\\2`,
|
|
101
|
+
const regex = new RegExp(
|
|
102
|
+
`\\b(${urlAttributes.join('|')}|data-[a-zA-Z0-9_-]+)\\s*=\\s*(['"])(?!https?:|//|mailto:|tel:|#)([^'"]+)\\2`,
|
|
133
103
|
'gi'
|
|
134
104
|
);
|
|
135
|
-
}
|
|
136
105
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
const updatedUrl = replacer(url);
|
|
141
|
-
return `${attribute}=${quote}${updatedUrl}${quote}`;
|
|
106
|
+
return html.replace(regex, (_match, attr, quote, url) => {
|
|
107
|
+
const newUrl = replacer(url);
|
|
108
|
+
return `${attr}=${quote}${newUrl}${quote}`;
|
|
142
109
|
});
|
|
143
110
|
}
|
|
144
|
-
|
|
145
|
-
function getRelativeUrlCssRegex() {
|
|
146
|
-
const ignoredProtocols = ['https?:', '//', 'data:', 'mailto:', 'tel:', '#'].join('|');
|
|
147
|
-
return new RegExp(
|
|
148
|
-
`url\\(\\s*(['"]?)(?!${ignoredProtocols})([^'")]+)\\1\\s*\\)`,
|
|
149
|
-
'gi'
|
|
150
|
-
);
|
|
151
|
-
}
|
|
111
|
+
|
|
152
112
|
|
|
153
113
|
function replaceRelativeUrlsInCss(css, replacer) {
|
|
154
|
-
const regex =
|
|
155
|
-
|
|
156
|
-
return css.replace(regex, (_match, quote, url) => {
|
|
157
|
-
const updatedUrl = replacer(url);
|
|
158
|
-
return `url(${quote}${updatedUrl}${quote})`;
|
|
159
|
-
});
|
|
160
|
-
}
|
|
114
|
+
const regex = /url\(\s*(['"]?)(.+?)\1\s*\)/gi;
|
|
161
115
|
|
|
162
|
-
|
|
163
|
-
|
|
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
|
+
});
|
|
164
123
|
}
|
|
165
124
|
|
|
166
125
|
function replaceRelativeUrlsInHtml(html, baseUrl) {
|
|
167
|
-
return replaceRelativeUrls(html, (url) =>
|
|
126
|
+
return replaceRelativeUrls(html, (url) => {
|
|
127
|
+
return new URL(url, baseUrl).href;
|
|
128
|
+
});
|
|
168
129
|
}
|
|
169
|
-
|
|
130
|
+
|
|
170
131
|
function replaceRelativeUrlsInCssWithBase(css, cssFileUrl) {
|
|
171
|
-
return replaceRelativeUrlsInCss(css, (url) =>
|
|
172
|
-
|
|
173
|
-
|
|
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
|
+
|
|
174
140
|
const parseRequirements = async (files, options) => {
|
|
175
141
|
const { source } = options;
|
|
176
142
|
let output = '';
|
|
@@ -198,14 +164,13 @@ const block = async (
|
|
|
198
164
|
return output;
|
|
199
165
|
};
|
|
200
166
|
|
|
201
|
-
const convertDashesSpacesAndUppercaseToUnderscoresAndLowercase = (
|
|
202
|
-
if (
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
};
|
|
167
|
+
const convertDashesSpacesAndUppercaseToUnderscoresAndLowercase = (string) => {
|
|
168
|
+
if (string) {
|
|
169
|
+
return `${string.replaceAll('-', '_').replaceAll(' ', '_').toLowerCase()}`;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return '';
|
|
173
|
+
};
|
|
209
174
|
|
|
210
175
|
const newName = replaceUnderscoresSpacesAndUppercaseLetters(name);
|
|
211
176
|
const blockName = `${sanitizeAndReplaceLeadingNumbers(replaceUnderscoresSpacesAndUppercaseLetters(prefix))}/${sanitizeAndReplaceLeadingNumbers(replaceUnderscoresSpacesAndUppercaseLetters(name))}`;
|
|
@@ -420,14 +385,39 @@ const block = async (
|
|
|
420
385
|
`;
|
|
421
386
|
};
|
|
422
387
|
|
|
423
|
-
function
|
|
424
|
-
|
|
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, {
|
|
425
411
|
presets: [[presetReact, { pragma: 'wp.element.createElement' }]],
|
|
426
|
-
filename: 'block.js'
|
|
427
|
-
};
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
412
|
+
filename: 'block.js'
|
|
413
|
+
});
|
|
414
|
+
} catch (error) {
|
|
415
|
+
console.log(error);
|
|
416
|
+
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
return test;
|
|
420
|
+
}
|
|
431
421
|
|
|
432
422
|
const saveFiles = async (options) => {
|
|
433
423
|
const { cssFiles = [], jsFiles = [], shouldSaveFiles, name, prefix } = options;
|
|
@@ -442,33 +432,57 @@ const block = async (
|
|
|
442
432
|
|
|
443
433
|
css += await parseRequirements(cssFiles, options);
|
|
444
434
|
|
|
435
|
+
console.log('[CSS BEFORE]', css);
|
|
436
|
+
|
|
437
|
+
|
|
445
438
|
for (const style of styles) {
|
|
446
439
|
css += style.content;
|
|
447
440
|
}
|
|
448
441
|
|
|
442
|
+
css = css.replace(/[^{}]+:is\([^)]*\[.*?['"].*?\)[^{}]*\{[^}]*\}/g, '');
|
|
443
|
+
|
|
449
444
|
const scopedCssFrontend = scopeCss(css, `.wp-block-${sanitizeAndReplaceLeadingNumbers(replaceUnderscoresSpacesAndUppercaseLetters(prefix))}-${sanitizeAndReplaceLeadingNumbers(replaceUnderscoresSpacesAndUppercaseLetters(name))}`);
|
|
450
445
|
const editorStyleFile = scopeCss(css, `[data-type="${blockName}"]`);
|
|
451
446
|
const scriptFile = js;
|
|
452
|
-
let blockCode = await getBlock(htmlContent, options);
|
|
453
447
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
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')
|
|
458
457
|
|
|
459
458
|
const indexFile = getPhp(options);
|
|
459
|
+
let blockFile = '';
|
|
460
460
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
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
|
+
}
|
|
464
470
|
|
|
471
|
+
console.log(blockFile);
|
|
472
|
+
|
|
473
|
+
|
|
465
474
|
if (shouldSaveFiles) {
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
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
|
+
}
|
|
472
486
|
}
|
|
473
487
|
|
|
474
488
|
return {
|
|
@@ -497,22 +511,14 @@ const block = async (
|
|
|
497
511
|
return `${prefix}${suffix}`;
|
|
498
512
|
};
|
|
499
513
|
|
|
500
|
-
const functionSufix = generateRandomVariableName('func'
|
|
514
|
+
const functionSufix = generateRandomVariableName('func');
|
|
501
515
|
|
|
502
516
|
const setRandomAttributeContent = (randomVariableName, content) => {
|
|
503
|
-
let newContent = content;
|
|
504
517
|
const isArray = Array.isArray(content);
|
|
518
|
+
|
|
505
519
|
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
if (match === '"') {
|
|
509
|
-
return '"';
|
|
510
|
-
}
|
|
511
|
-
return match;
|
|
512
|
-
})
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
attributes[randomVariableName] = { type: isArray ? 'array' : 'string', default: newContent };
|
|
520
|
+
attributes[randomVariableName] = { type: isArray ? 'array' : 'string', default: content };
|
|
521
|
+
|
|
516
522
|
};
|
|
517
523
|
|
|
518
524
|
const hasAbsoluteKeyword = (str) => {
|
|
@@ -641,11 +647,29 @@ const block = async (
|
|
|
641
647
|
const newPrefix = prefix ? replaceUnderscoresSpacesAndUppercaseLetters(prefix) : 'wp';
|
|
642
648
|
const randomVariable = generateRandomVariableName(`${type}${newPrefix}`);
|
|
643
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
|
+
}
|
|
644
668
|
attributes[randomVariable] = {
|
|
645
669
|
attribute,
|
|
646
670
|
type: 'string',
|
|
647
671
|
selector: 'img',
|
|
648
|
-
default: attribute === 'alt' ? imgAlt :
|
|
672
|
+
default: attribute === 'alt' ? imgAlt : `{vars.url}${imgSrcNoLeadingSlash}`.replace(/^\u007f/, '$'),
|
|
649
673
|
};
|
|
650
674
|
|
|
651
675
|
imgTag.attr(attribute, `{attributes.${randomVariable}}`);
|
|
@@ -774,31 +798,25 @@ const block = async (
|
|
|
774
798
|
};
|
|
775
799
|
|
|
776
800
|
const getFixedHtml = (html) => {
|
|
777
|
-
const onChangeRegex = / onChange="{" \(newtext\)=""\>/gi;
|
|
778
|
-
const closeRichTextRegex = /<\/RichText>/gi;
|
|
779
|
-
const valueBindingRegex = /value="{(.*?)}"/gi;
|
|
780
|
-
const attributeBindingRegex = /"{attributes\.(.*?)}"/gi;
|
|
781
|
-
|
|
782
|
-
const fixOnChange = ' onChange={ (newtext) => ';
|
|
783
|
-
const fixValueBinding = 'value={$1}';
|
|
784
|
-
const fixAttributeBinding = '{attributes.$1}';
|
|
785
|
-
|
|
786
801
|
return html
|
|
787
|
-
.replace(
|
|
788
|
-
.replace(
|
|
789
|
-
.replace(
|
|
790
|
-
.replace(
|
|
802
|
+
.replace(/ onChange="{" \(newtext\)=""\>/gi, ' onChange={ (newtext) => ')
|
|
803
|
+
.replace(/\<\/RichText\>/gi, '')
|
|
804
|
+
.replace(/value="{(.*?)}"/gi, 'value={$1}')
|
|
805
|
+
.replace(/"{attributes.(.*?)}"/gi, '{attributes.$1}');
|
|
791
806
|
};
|
|
792
|
-
|
|
793
807
|
|
|
794
808
|
const processImages = (imgTag) => {
|
|
795
809
|
const properties = getImageProperties(imgTag);
|
|
796
|
-
const
|
|
797
|
-
|
|
798
|
-
processImage({ ...properties, type });
|
|
799
|
-
};
|
|
810
|
+
const { isBackground } = properties;
|
|
800
811
|
|
|
801
|
-
|
|
812
|
+
if (!isBackground) {
|
|
813
|
+
processImage({ ...properties, type: 'image' });
|
|
814
|
+
|
|
815
|
+
return;
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
processImage({ ...properties, type: 'background' });
|
|
819
|
+
};
|
|
802
820
|
const loopImages = ($) => {
|
|
803
821
|
$('img').each((_index, img) => {
|
|
804
822
|
processImages($(img));
|
|
@@ -834,138 +852,85 @@ const block = async (
|
|
|
834
852
|
return getRichTextTemplate(randomVariable, variableContent);
|
|
835
853
|
};
|
|
836
854
|
|
|
837
|
-
const containsCodeSyntax = (text) => {
|
|
838
|
-
const codePattern = /{|}|\(|\)|=>/;
|
|
839
|
-
return codePattern.test(text.trim());
|
|
840
|
-
};
|
|
841
|
-
|
|
842
|
-
const isEmptyText = (text) => {
|
|
843
|
-
return text.trim() === '';
|
|
844
|
-
};
|
|
845
|
-
|
|
846
855
|
const parseContent = (content) => {
|
|
847
|
-
return content.replace(/>([^<]+)</g, (match,
|
|
848
|
-
const
|
|
849
|
-
|
|
856
|
+
return content.replace(/>([^<]+)</g, (match, variableContent) => {
|
|
857
|
+
const regex = /{|}|\(|\)|=>/;
|
|
858
|
+
|
|
859
|
+
if (regex.test(variableContent.trim())) {
|
|
860
|
+
return match;
|
|
861
|
+
}
|
|
850
862
|
|
|
851
|
-
if (
|
|
863
|
+
if (variableContent.trim() === '') {
|
|
852
864
|
return match;
|
|
853
865
|
}
|
|
854
866
|
|
|
855
|
-
return convertToRichText(
|
|
867
|
+
return convertToRichText(variableContent);
|
|
856
868
|
});
|
|
857
869
|
};
|
|
858
|
-
|
|
859
|
-
const removeHtmlComments = (html) => {
|
|
860
|
-
return html.replaceAll(/<!--(.*?)-->/gs, '');
|
|
861
|
-
};
|
|
862
|
-
|
|
863
|
-
const wrapWithContainer = (html) => {
|
|
864
|
-
return `<div className="custom-block">${html}</div>`;
|
|
865
|
-
};
|
|
866
|
-
|
|
867
|
-
const getEditJsxContent = async (options) => {
|
|
868
|
-
const dynamicContent = transformFormToDynamicJSX(htmlContent);
|
|
869
|
-
const withoutComments = removeHtmlComments(dynamicContent);
|
|
870
|
-
const wrappedContent = wrapWithContainer(withoutComments);
|
|
871
|
-
|
|
872
|
-
const parsedContent = convert(parseContent(wrappedContent));
|
|
873
870
|
|
|
871
|
+
const getEditJsxContent = async (options) => {
|
|
872
|
+
let content = transformFormToDynamicJSX(options.htmlContent);
|
|
873
|
+
|
|
874
|
+
content = content.replaceAll(/<!--(.*?)-->/gs, '');
|
|
875
|
+
|
|
876
|
+
content = `<div className="custom-block">${content}</div>`;
|
|
877
|
+
|
|
874
878
|
return await processEditImages({
|
|
875
879
|
...options,
|
|
876
|
-
htmlContent:
|
|
880
|
+
htmlContent: parseContent(content),
|
|
877
881
|
});
|
|
878
882
|
};
|
|
879
|
-
|
|
880
|
-
const hasAttributes = (panel) => {
|
|
881
|
-
return Array.isArray(panel.attributes) && panel.attributes.length > 0;
|
|
882
|
-
};
|
|
883
883
|
|
|
884
|
-
const createPanel = (
|
|
885
|
-
if (
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
};
|
|
889
|
-
|
|
890
|
-
const getSvgTemplate = (_match, attributesString, closingTag, variableName) => {
|
|
891
|
-
return `
|
|
892
|
-
<svg
|
|
893
|
-
${attributesString}
|
|
894
|
-
dangerouslySetInnerHTML={{ __html: attributes.${variableName} }}
|
|
895
|
-
>
|
|
896
|
-
${closingTag}
|
|
897
|
-
`;
|
|
884
|
+
const createPanel = (values) => {
|
|
885
|
+
if (values.attributes && values.attributes.length > 0) {
|
|
886
|
+
panels.push(values);
|
|
887
|
+
}
|
|
898
888
|
};
|
|
899
889
|
|
|
900
|
-
const
|
|
901
|
-
|
|
902
|
-
return [...html.matchAll(svgRegex)];
|
|
903
|
-
};
|
|
904
|
-
|
|
905
|
-
const parseSVGMatch = (match) => {
|
|
906
|
-
const [fullSvg, attributes, content, closingTag] = match;
|
|
907
|
-
const start = match.index;
|
|
908
|
-
const end = start + fullSvg.length;
|
|
909
|
-
|
|
910
|
-
return { fullSvg, attributes, content, closingTag, start, end };
|
|
890
|
+
const getSvgTemplate = (_match, group1, _group3, randomSVGVariable) => {
|
|
891
|
+
return `<svg ${group1} dangerouslySetInnerHTML={ { __html: attributes.${randomSVGVariable} }}></svg>`;
|
|
911
892
|
};
|
|
912
|
-
|
|
913
|
-
const hasContent = (content) => content.trim() !== '';
|
|
914
|
-
|
|
915
|
-
const handleSVGContent = (content) => {
|
|
916
|
-
const variableName = generateRandomVariableName('svg');
|
|
917
|
-
const cleanedContent = content.replaceAll('className', 'class');
|
|
918
|
-
|
|
919
|
-
setRandomAttributeContent(variableName, cleanedContent);
|
|
920
|
-
|
|
921
|
-
createPanel({
|
|
922
|
-
type: 'svg',
|
|
923
|
-
title: 'SVG Markup',
|
|
924
|
-
attributes: [variableName],
|
|
925
|
-
});
|
|
926
|
-
|
|
927
|
-
return variableName;
|
|
928
|
-
};
|
|
929
|
-
|
|
930
|
-
const transformSVG = async (fullSvg, attributes, closingTag, variableName) => {
|
|
931
|
-
const template = getSvgTemplate(fullSvg, attributes, closingTag, variableName);
|
|
932
|
-
return await transform(template, { jsxRuntime: 'classic' });
|
|
933
|
-
};
|
|
934
|
-
|
|
935
893
|
const replaceSVGImages = async (html) => {
|
|
936
|
-
const
|
|
894
|
+
const regex = /<\s*svg\b((?:[^>'"]|"[^"]*"|'[^']*')*)>(\s*(?:[^<]|<(?!\/svg\s*>))*)(<\/\s*svg\s*>)/gim;
|
|
937
895
|
|
|
938
|
-
let
|
|
939
|
-
let
|
|
896
|
+
let result = '';
|
|
897
|
+
let lastIndex = 0;
|
|
898
|
+
const matches = [...html.matchAll(regex)];
|
|
940
899
|
|
|
941
900
|
for (const match of matches) {
|
|
942
|
-
const
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
901
|
+
const [fullMatch, group1, group2, group3] = match;
|
|
902
|
+
const start = match.index;
|
|
903
|
+
const end = start + fullMatch.length;
|
|
904
|
+
|
|
905
|
+
result += html.slice(lastIndex, start);
|
|
906
|
+
|
|
907
|
+
const content = group2.trim();
|
|
908
|
+
if (content) {
|
|
909
|
+
const randomSVGVariable = generateRandomVariableName('svg');
|
|
910
|
+
setRandomAttributeContent(randomSVGVariable, content.replaceAll('className', 'class'));
|
|
911
|
+
createPanel({
|
|
912
|
+
type: 'svg',
|
|
913
|
+
title: 'SVG Markup',
|
|
914
|
+
attributes: [randomSVGVariable],
|
|
915
|
+
});
|
|
916
|
+
|
|
955
917
|
|
|
956
|
-
|
|
918
|
+
const replacement = getSvgTemplate(fullMatch, group1, group3, randomSVGVariable)
|
|
919
|
+
|
|
920
|
+
result += replacement;
|
|
957
921
|
} else {
|
|
958
|
-
|
|
922
|
+
result += fullMatch;
|
|
959
923
|
}
|
|
960
924
|
|
|
961
|
-
|
|
925
|
+
lastIndex = end;
|
|
962
926
|
}
|
|
963
927
|
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
928
|
+
result += html.slice(lastIndex);
|
|
929
|
+
|
|
930
|
+
console.log(result);
|
|
931
|
+
|
|
932
|
+
return result;
|
|
967
933
|
};
|
|
968
|
-
|
|
969
934
|
const getSvgPanelTemplate = (panel) => {
|
|
970
935
|
return panel.attributes && attributes[panel.attributes]
|
|
971
936
|
? `
|
|
@@ -1004,10 +969,7 @@ const block = async (
|
|
|
1004
969
|
<PanelRow>
|
|
1005
970
|
<div>
|
|
1006
971
|
<MediaUpload
|
|
1007
|
-
onSelect={ (media) => {
|
|
1008
|
-
setAttributes({
|
|
1009
|
-
${mediaAtts}
|
|
1010
|
-
});
|
|
972
|
+
onSelect={ (media) => { setAttributes({ ${mediaAtts} });
|
|
1011
973
|
} }
|
|
1012
974
|
type="image"
|
|
1013
975
|
value={ attributes.${panel.attributes?.[0]} }
|
|
@@ -1316,7 +1278,7 @@ const block = async (
|
|
|
1316
1278
|
/<RichText((.|\n)*?)value=\{(.*?)\}((.|\n)*?)\/>/gi,
|
|
1317
1279
|
'<RichText.Content value={$3} />'
|
|
1318
1280
|
)
|
|
1319
|
-
.
|
|
1281
|
+
.replaceAll('class=', 'className=')
|
|
1320
1282
|
.replace(
|
|
1321
1283
|
/<MediaUpload\b[^>]*>([\s\S]*?(<img\b[^>]*>*\/>)[\s\S]*?)\/>/g,
|
|
1322
1284
|
(_match, _attributes, img) => {
|
|
@@ -1353,108 +1315,71 @@ const block = async (
|
|
|
1353
1315
|
};
|
|
1354
1316
|
};
|
|
1355
1317
|
|
|
1356
|
-
const mergeAttributes = (attrString) => {
|
|
1357
|
-
const attrRegex = /(\S+)=["'](.*?)["']/g;
|
|
1358
|
-
const attrs = {};
|
|
1359
|
-
let match;
|
|
1360
|
-
|
|
1361
|
-
while ((match = attrRegex.exec(attrString)) !== null) {
|
|
1362
|
-
attrs[match[1]] = match[2];
|
|
1363
|
-
}
|
|
1364
|
-
return attrs;
|
|
1365
|
-
};
|
|
1366
|
-
|
|
1367
1318
|
const unwrapAnchor = (htmlContent) => {
|
|
1368
|
-
|
|
1369
1319
|
return htmlContent.replace(
|
|
1370
1320
|
/<span([^>]*)>\s*<a([^>]*)>(.*?)<\/a>\s*<\/span>/gi,
|
|
1371
1321
|
(_, spanAttrs, anchorAttrs, content) => {
|
|
1372
|
-
const
|
|
1373
|
-
const
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1322
|
+
const allAttrs = {};
|
|
1323
|
+
const attrRegex = /(\S+)=["'](.*?)["']/g;
|
|
1324
|
+
|
|
1325
|
+
let match;
|
|
1326
|
+
while ((match = attrRegex.exec(spanAttrs)) !== null) {
|
|
1327
|
+
allAttrs[match[1]] = match[2];
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
while ((match = attrRegex.exec(anchorAttrs)) !== null) {
|
|
1331
|
+
allAttrs[match[1]] = match[2];
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
return `<a ${Object.entries(allAttrs)
|
|
1377
1335
|
.map(([key, value]) => `${key}="${value}"`)
|
|
1378
|
-
.join(' ')
|
|
1379
|
-
|
|
1380
|
-
return `<a ${attrString}>${content}</a>`;
|
|
1336
|
+
.join(' ')}>${content}</a>`;
|
|
1381
1337
|
}
|
|
1382
1338
|
);
|
|
1383
1339
|
};
|
|
1384
|
-
|
|
1385
1340
|
|
|
1386
1341
|
const getComponentAttributes = () => {
|
|
1387
|
-
return
|
|
1342
|
+
return Object.entries(attributes)
|
|
1343
|
+
.map(([key, value]) => {
|
|
1344
|
+
if (typeof value === 'object' && value !== null) {
|
|
1345
|
+
return `${key}: { ${Object.entries(value).map(([k, v]) => `${k}: \`${v}\``).join(', ')} }`;
|
|
1346
|
+
} else {
|
|
1347
|
+
return `${key}:\`${value}\``;
|
|
1348
|
+
}
|
|
1349
|
+
})
|
|
1350
|
+
.join(',\n');
|
|
1388
1351
|
};
|
|
1389
1352
|
|
|
1390
1353
|
const getEdit = async (options) => {
|
|
1391
1354
|
let { htmlContent } = options;
|
|
1392
1355
|
|
|
1393
|
-
if (
|
|
1394
|
-
|
|
1356
|
+
if (htmlContent) {
|
|
1357
|
+
options.htmlContent = unwrapBody(htmlContent);
|
|
1358
|
+
const postProcessLinks = processLinks(options);
|
|
1359
|
+
const postGetEditJsx = await getEditJsxContent(postProcessLinks);
|
|
1360
|
+
const preConvert = await postGetEditJsx.replace(/<\/br>/g, '<br/>').replace(/<\/hr>/g, '<hr/>')
|
|
1361
|
+
return convert(preConvert)
|
|
1395
1362
|
}
|
|
1396
1363
|
|
|
1397
|
-
|
|
1398
|
-
const jsxContent = await getEditJsxContent(processedOptions);
|
|
1399
|
-
|
|
1400
|
-
const replacedContent = jsxContent
|
|
1401
|
-
.replace(/<article\b([^>]*)>/gi, '<div$1>')
|
|
1402
|
-
.replace(/<\/article>/gi, '</div>')
|
|
1403
|
-
.replaceAll('../', '')
|
|
1404
|
-
.replace(/style="([^"]+)"/g, (_, styleString) => {
|
|
1405
|
-
const styleObj = parseInlineStyle(styleString);
|
|
1406
|
-
return `style={${styleObj}}`;
|
|
1407
|
-
});
|
|
1408
|
-
|
|
1409
|
-
return await replaceSVGImages(replacedContent);
|
|
1364
|
+
return '';
|
|
1410
1365
|
};
|
|
1411
|
-
|
|
1412
1366
|
|
|
1413
1367
|
const parseBlockAttributes = () => {
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
return jsonString
|
|
1418
|
-
.replace(/"var.url\+\'(.*?)\'(.*?)"/g, "vars.url+'$1'$2")
|
|
1419
|
-
.replace(/var(.*?).url\+'(http.*?)'/g, 'http$2');
|
|
1420
|
-
};
|
|
1421
|
-
|
|
1422
|
-
const fixDangerouslySetInnerHTML = (edit) => {
|
|
1423
|
-
const pattern = /dangerouslySetInnerHTML="{" {="" __html:="" (.*?)="" }}=""/gm;
|
|
1424
|
-
const replacement = 'dangerouslySetInnerHTML={{ __html: $1 }}';
|
|
1368
|
+
const attrs = `{${getComponentAttributes()}}`;
|
|
1369
|
+
return replaceSourceUrlVars(attrs, options.source);
|
|
1370
|
+
}
|
|
1425
1371
|
|
|
1426
|
-
|
|
1427
|
-
};
|
|
1428
|
-
|
|
1429
|
-
const getBlock = async (htmlContent, settings) => {
|
|
1372
|
+
const getBlock = async (settings) => {
|
|
1430
1373
|
let {
|
|
1431
1374
|
name,
|
|
1432
1375
|
category,
|
|
1433
1376
|
generateIconPreview,
|
|
1434
|
-
basePath,
|
|
1435
|
-
cssFiles = [],
|
|
1436
|
-
jsFiles = [],
|
|
1437
1377
|
} = settings;
|
|
1438
1378
|
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
edit = fixDangerouslySetInnerHTML(edit);
|
|
1443
|
-
|
|
1379
|
+
const iconPreview = generateIconPreview ? `(<img src={vars.url + 'preview.jpeg'} />)` : "'shield'";
|
|
1380
|
+
const edit = await getEdit(settings);
|
|
1444
1381
|
const save = buildSaveContent(edit);
|
|
1445
|
-
const blockPanels = createPanels();
|
|
1446
|
-
const blockAttributes = parseBlockAttributes();
|
|
1447
|
-
|
|
1448
|
-
if (generateIconPreview) {
|
|
1449
|
-
try {
|
|
1450
|
-
await icon(htmlContent, { basePath, cssFiles, jsFiles });
|
|
1451
|
-
iconPreview = `(<img src="data:image/jpeg;base64,${await imageToBase64(
|
|
1452
|
-
path.join(basePath, 'preview.jpeg')
|
|
1453
|
-
)}" />)`;
|
|
1454
|
-
} catch (error) {
|
|
1455
|
-
console.log(`There was an error generating preview. ${error.message}`);
|
|
1456
|
-
}
|
|
1457
|
-
}
|
|
1382
|
+
const blockPanels = createPanels();
|
|
1458
1383
|
|
|
1459
1384
|
const output = `
|
|
1460
1385
|
(function () {
|
|
@@ -1464,7 +1389,7 @@ const block = async (
|
|
|
1464
1389
|
title: '${name}',
|
|
1465
1390
|
icon: ${iconPreview},
|
|
1466
1391
|
category: '${category}',
|
|
1467
|
-
attributes: ${
|
|
1392
|
+
attributes: ${parseBlockAttributes()},
|
|
1468
1393
|
edit(props) {
|
|
1469
1394
|
const { attributes, setAttributes } = props;
|
|
1470
1395
|
|
|
@@ -1488,16 +1413,13 @@ const block = async (
|
|
|
1488
1413
|
});
|
|
1489
1414
|
})();`;
|
|
1490
1415
|
|
|
1491
|
-
if (generateIconPreview) {
|
|
1492
|
-
return output.replace(/icon: \s * (')([^']*)(')/, 'icon: $2');
|
|
1493
|
-
}
|
|
1494
|
-
|
|
1495
1416
|
return output;
|
|
1496
1417
|
};
|
|
1497
1418
|
|
|
1498
1419
|
const setupVariables = async (htmlContent, options) => {
|
|
1499
|
-
|
|
1500
|
-
|
|
1420
|
+
|
|
1421
|
+
const styleRegex = /<style[^>]*>([\s\S]*?)<\/style>/gi;
|
|
1422
|
+
const linkRegex = /<link\b[^>]*((\brel=["']stylesheet["'])|\bhref=["'][^"']+\.css["'])[^>]*>/gi;
|
|
1501
1423
|
|
|
1502
1424
|
let match;
|
|
1503
1425
|
|
|
@@ -1523,7 +1445,11 @@ const block = async (
|
|
|
1523
1445
|
css += styles.map((style) => {
|
|
1524
1446
|
return `${style.content}`;
|
|
1525
1447
|
}).join('\n');
|
|
1526
|
-
|
|
1448
|
+
|
|
1449
|
+
|
|
1450
|
+
console.log('[CSSFETCHED]', css);
|
|
1451
|
+
|
|
1452
|
+
|
|
1527
1453
|
|
|
1528
1454
|
const scriptRegex = /<script[^>]*>([\s\S]*?)<\/script>/gi;
|
|
1529
1455
|
const scriptSrcRegex =
|
|
@@ -1537,9 +1463,7 @@ const block = async (
|
|
|
1537
1463
|
}
|
|
1538
1464
|
return '';
|
|
1539
1465
|
});
|
|
1540
|
-
|
|
1541
|
-
$('head').remove();
|
|
1542
|
-
htmlContent = $.html({ xml: false, decodeEntities: false });
|
|
1466
|
+
|
|
1543
1467
|
const fetchJsPromises = [];
|
|
1544
1468
|
|
|
1545
1469
|
while ((jsMatch = scriptSrcRegex.exec(htmlContent)) !== null) {
|
|
@@ -1566,6 +1490,18 @@ const block = async (
|
|
|
1566
1490
|
|
|
1567
1491
|
const newDir = path.join(basePath, replaceUnderscoresSpacesAndUppercaseLetters(name));
|
|
1568
1492
|
|
|
1493
|
+
const $ = cheerio.load(htmlContent, {
|
|
1494
|
+
xmlMode: true,
|
|
1495
|
+
decodeEntities: false,
|
|
1496
|
+
});
|
|
1497
|
+
|
|
1498
|
+
$('head, script, style').remove();
|
|
1499
|
+
|
|
1500
|
+
htmlContent = $('body').html();
|
|
1501
|
+
|
|
1502
|
+
options.html
|
|
1503
|
+
|
|
1504
|
+
|
|
1569
1505
|
try {
|
|
1570
1506
|
fs.mkdirSync(newDir, { recursive: true });
|
|
1571
1507
|
|
|
@@ -1585,7 +1521,14 @@ const block = async (
|
|
|
1585
1521
|
htmlContent = replaceRelativeUrlsInHtml(htmlContent, source);
|
|
1586
1522
|
htmlContent = replaceRelativeUrlsInCssWithBase(htmlContent, source);
|
|
1587
1523
|
}
|
|
1524
|
+
|
|
1525
|
+
try {
|
|
1526
|
+
icon(htmlContent, { basePath: path.join(options.basePath, replaceUnderscoresSpacesAndUppercaseLetters(options.name)) });
|
|
1527
|
+
} catch (error) {
|
|
1528
|
+
console.log(`There was an error generating preview. ${error.message}`);
|
|
1529
|
+
}
|
|
1588
1530
|
|
|
1589
1531
|
return saveFiles(await setupVariables(htmlContent, options));
|
|
1590
1532
|
};
|
|
1533
|
+
|
|
1591
1534
|
export default block;
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"@babel/preset-react": "^7.28.5",
|
|
5
5
|
"@svgr/core": "^8.1.0",
|
|
6
6
|
"cheerio": "^1.1.2",
|
|
7
|
-
"css-scoping": "^1.0.
|
|
7
|
+
"css-scoping": "^1.0.1",
|
|
8
8
|
"fetch-page-assets": "^1.2.5",
|
|
9
9
|
"fs": "^0.0.1-security",
|
|
10
10
|
"html-screenshots": "^1.2.7",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"type": "module",
|
|
24
24
|
"types": "index.ts",
|
|
25
25
|
"name": "html-to-gutenberg",
|
|
26
|
-
"version": "4.2.
|
|
26
|
+
"version": "4.2.5",
|
|
27
27
|
"description": "Transform any valid HTML string into fully editable WP Gutenberg blocks in seconds rather than hours.",
|
|
28
28
|
"main": "index.js",
|
|
29
29
|
"directories": {
|
|
@@ -56,4 +56,4 @@
|
|
|
56
56
|
"url": "https://github.com/DiogoAngelim/html-to-gutenberg/issues"
|
|
57
57
|
},
|
|
58
58
|
"homepage": "https://www.html-to-gutenberg.io"
|
|
59
|
-
}
|
|
59
|
+
}
|