html-to-gutenberg 4.2.9 → 4.2.10
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.example +20 -3
- package/.github/workflows/sync-npm.yml +154 -0
- package/fetch-page-assets.test.ts +448 -0
- package/index.d.ts +173 -0
- package/index.js +570 -224
- package/index.test.ts +633 -4
- package/index.ts +168 -63
- package/package.json +25 -24
- package/r2.js +163 -0
- package/readme.md +122 -88
- package/scripts/patch-fetch-page-assets.mjs +13 -0
- package/scripts/sync-from-npm.mjs +115 -0
- package/tsconfig.json +17 -2
- package/vendor/fetch-page-assets/LICENSE.MD +21 -0
- package/vendor/fetch-page-assets/README.md +117 -0
- package/vendor/fetch-page-assets/index.js +362 -0
- package/vendor/fetch-page-assets/package.json +48 -0
- package/.env +0 -1
package/index.js
CHANGED
|
@@ -9,6 +9,13 @@ import dotenv from 'dotenv';
|
|
|
9
9
|
import { createRequire } from 'module';
|
|
10
10
|
import convert from 'node-html-to-jsx';
|
|
11
11
|
import path from 'path';
|
|
12
|
+
import {
|
|
13
|
+
createFileRecord,
|
|
14
|
+
createJobId,
|
|
15
|
+
inferContentType,
|
|
16
|
+
uploadBufferToR2,
|
|
17
|
+
zipEntriesToBuffer,
|
|
18
|
+
} from './r2.js';
|
|
12
19
|
|
|
13
20
|
import {
|
|
14
21
|
imports,
|
|
@@ -19,32 +26,411 @@ import {
|
|
|
19
26
|
const require = createRequire(import.meta.url);
|
|
20
27
|
const { version } = require('./package.json');
|
|
21
28
|
|
|
29
|
+
export const createProfiler = (enabled) => {
|
|
30
|
+
const marks = new Map();
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
start(label) {
|
|
34
|
+
if (enabled) {
|
|
35
|
+
marks.set(label, process.hrtime.bigint());
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
end(label) {
|
|
39
|
+
if (enabled && marks.has(label)) {
|
|
40
|
+
const start = marks.get(label);
|
|
41
|
+
const elapsedMs = Number(process.hrtime.bigint() - start) / 1e6;
|
|
42
|
+
console.log(`[profile] ${label}: ${elapsedMs.toFixed(2)}ms`);
|
|
43
|
+
marks.delete(label);
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export const findSelfClosingJsxEnd = (content, startIndex) => {
|
|
50
|
+
let inSingleQuote = false;
|
|
51
|
+
let inDoubleQuote = false;
|
|
52
|
+
let inTemplateQuote = false;
|
|
53
|
+
let braceDepth = 0;
|
|
54
|
+
let parenDepth = 0;
|
|
55
|
+
|
|
56
|
+
for (let i = startIndex; i < content.length - 1; i++) {
|
|
57
|
+
const char = content[i];
|
|
58
|
+
const next = content[i + 1];
|
|
59
|
+
const prev = i > 0 ? content[i - 1] : '';
|
|
60
|
+
const escaped = prev === '\\';
|
|
61
|
+
|
|
62
|
+
if (!escaped) {
|
|
63
|
+
if (!inDoubleQuote && !inTemplateQuote && char === '\'') {
|
|
64
|
+
inSingleQuote = !inSingleQuote;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (!inSingleQuote && !inTemplateQuote && char === '"') {
|
|
68
|
+
inDoubleQuote = !inDoubleQuote;
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
if (!inSingleQuote && !inDoubleQuote && char === '`') {
|
|
72
|
+
inTemplateQuote = !inTemplateQuote;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (inSingleQuote || inDoubleQuote || inTemplateQuote) {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (char === '{') {
|
|
82
|
+
braceDepth++;
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (char === '}') {
|
|
87
|
+
braceDepth = Math.max(0, braceDepth - 1);
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (char === '(') {
|
|
92
|
+
parenDepth++;
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (char === ')') {
|
|
97
|
+
parenDepth = Math.max(0, parenDepth - 1);
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (char === '/' && next === '>' && braceDepth === 0 && parenDepth === 0) {
|
|
102
|
+
return i + 2;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return -1;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
export const replaceSelfClosingJsxComponent = (content, componentName, replacer) => {
|
|
110
|
+
const openTag = `<${componentName}`;
|
|
111
|
+
|
|
112
|
+
if (!content.includes(openTag)) {
|
|
113
|
+
return content;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
let cursor = 0;
|
|
117
|
+
let result = '';
|
|
118
|
+
|
|
119
|
+
while (cursor < content.length) {
|
|
120
|
+
const start = content.indexOf(openTag, cursor);
|
|
121
|
+
|
|
122
|
+
if (start === -1) {
|
|
123
|
+
result += content.slice(cursor);
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
result += content.slice(cursor, start);
|
|
128
|
+
const end = findSelfClosingJsxEnd(content, start);
|
|
129
|
+
|
|
130
|
+
if (end === -1) {
|
|
131
|
+
result += content.slice(start);
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
result += replacer(content.slice(start, end));
|
|
136
|
+
cursor = end;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return result;
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
export const getMediaUploadSaveTemplate = (image) => {
|
|
143
|
+
if (!image) {
|
|
144
|
+
return '';
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const { randomUrlVariable, randomAltVariable, imgClass } = image;
|
|
148
|
+
const classNameAttr = imgClass ? ` className="${imgClass}"` : '';
|
|
149
|
+
|
|
150
|
+
return `<img src={attributes.${randomUrlVariable}} alt={attributes.${randomAltVariable}}${classNameAttr} />`;
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
export const replaceMediaUploadComponents = (content, imageRegistry) => {
|
|
154
|
+
let imageIndex = 0;
|
|
155
|
+
|
|
156
|
+
return replaceSelfClosingJsxComponent(content, 'MediaUpload', () => {
|
|
157
|
+
const template = getMediaUploadSaveTemplate(imageRegistry[imageIndex]);
|
|
158
|
+
imageIndex++;
|
|
159
|
+
return template;
|
|
160
|
+
});
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
export const replaceRichTextComponents = (content) => {
|
|
164
|
+
return replaceSelfClosingJsxComponent(content, 'RichText', (componentSource) => {
|
|
165
|
+
const valueMatch = componentSource.match(/\bvalue=\{([^}]+)\}/);
|
|
166
|
+
|
|
167
|
+
if (!valueMatch) {
|
|
168
|
+
return componentSource;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return `<RichText.Content value={${valueMatch[1]}} />`;
|
|
172
|
+
});
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
export const buildAssetExtractionOptions = (basePath, options = {}) => ({
|
|
176
|
+
basePath,
|
|
177
|
+
saveFile: false,
|
|
178
|
+
verbose: false,
|
|
179
|
+
maxRetryAttempts: 1,
|
|
180
|
+
retryDelay: 0,
|
|
181
|
+
concurrency: 8,
|
|
182
|
+
uploadToR2: options.uploadToR2 || false,
|
|
183
|
+
returnDetails: options.returnDetails || false,
|
|
184
|
+
jobId: options.jobId,
|
|
185
|
+
r2Prefix: options.r2Prefix,
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
export const slugifyBlockValue = (value = '') => {
|
|
189
|
+
return String(value)
|
|
190
|
+
.replace(/\W|_/g, '-')
|
|
191
|
+
.replace(/-+/g, '-')
|
|
192
|
+
.replace(/^-|-$/g, '')
|
|
193
|
+
.toLowerCase();
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
export const formatCategoryLabel = (category = '') => {
|
|
197
|
+
return String(category)
|
|
198
|
+
.split(/[-_\s]+/)
|
|
199
|
+
.filter(Boolean)
|
|
200
|
+
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
201
|
+
.join(' ');
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
export const normalizeBlockOptions = (options = {}) => {
|
|
205
|
+
const hasOwn = (key) => Object.prototype.hasOwnProperty.call(options, key);
|
|
206
|
+
const title = options.title ?? options.name ?? 'My block';
|
|
207
|
+
const slug = slugifyBlockValue(options.slug ?? title) || 'my-block';
|
|
208
|
+
const namespace = options.namespace ?? options.prefix ?? 'wp';
|
|
209
|
+
const baseUrl = options.baseUrl ?? options.source ?? null;
|
|
210
|
+
const outputPath = options.outputPath ?? options.basePath ?? process.cwd();
|
|
211
|
+
const hasExplicitLocalOutputPreference =
|
|
212
|
+
hasOwn('writeFiles') ||
|
|
213
|
+
hasOwn('outputPath') ||
|
|
214
|
+
hasOwn('shouldSaveFiles') ||
|
|
215
|
+
hasOwn('basePath');
|
|
216
|
+
const hasExplicitJobPreference = hasOwn('uploadToR2') || hasOwn('jobId');
|
|
217
|
+
const outputMode =
|
|
218
|
+
options.outputMode ??
|
|
219
|
+
(hasExplicitJobPreference ? 'job' : hasExplicitLocalOutputPreference ? 'legacy' : 'job');
|
|
220
|
+
const writeFiles = options.writeFiles ?? options.shouldSaveFiles ?? (outputMode === 'legacy');
|
|
221
|
+
const generatePreviewImage =
|
|
222
|
+
options.generatePreviewImage ?? options.generateIconPreview ?? false;
|
|
223
|
+
|
|
224
|
+
return {
|
|
225
|
+
...options,
|
|
226
|
+
title,
|
|
227
|
+
name: title,
|
|
228
|
+
slug,
|
|
229
|
+
namespace,
|
|
230
|
+
prefix: namespace,
|
|
231
|
+
baseUrl,
|
|
232
|
+
source: baseUrl,
|
|
233
|
+
category: options.category ?? 'common',
|
|
234
|
+
registerCategoryIfMissing: options.registerCategoryIfMissing ?? false,
|
|
235
|
+
outputPath,
|
|
236
|
+
basePath: outputPath,
|
|
237
|
+
writeFiles,
|
|
238
|
+
shouldSaveFiles: writeFiles,
|
|
239
|
+
generatePreviewImage,
|
|
240
|
+
generateIconPreview: generatePreviewImage,
|
|
241
|
+
jsFiles: options.jsFiles ?? [],
|
|
242
|
+
cssFiles: options.cssFiles ?? [],
|
|
243
|
+
outputMode,
|
|
244
|
+
uploadToR2: options.uploadToR2 ?? outputMode === 'job',
|
|
245
|
+
jobId: options.jobId,
|
|
246
|
+
};
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
export const replaceRelativeUrls = (html, replacer) => {
|
|
250
|
+
const urlAttributes = [
|
|
251
|
+
'src', 'href', 'action', 'srcset', 'poster', 'data', 'formaction'
|
|
252
|
+
];
|
|
253
|
+
|
|
254
|
+
const regex = new RegExp(
|
|
255
|
+
`\\b(${urlAttributes.join('|')}|data-[a-zA-Z0-9_-]+)\\s*=\\s*(['"])(?!https?:|//|mailto:|tel:|#)([^'"]+)\\2`,
|
|
256
|
+
'gi'
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
return html.replace(regex, (_match, attr, quote, url) => {
|
|
260
|
+
const newUrl = replacer(url);
|
|
261
|
+
return `${attr}=${quote}${newUrl}${quote}`;
|
|
262
|
+
});
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
export const replaceRelativeUrlsInCss = (css, replacer) => {
|
|
266
|
+
const regex = /url\(\s*(['"]?)(.+?)\1\s*\)/gi;
|
|
267
|
+
|
|
268
|
+
return css.replace(regex, (match, quote, url) => {
|
|
269
|
+
if (/^(https?:|\/\/|data:|mailto:|tel:|#)/.test(url.trim())) {
|
|
270
|
+
return match;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const newUrl = replacer(url);
|
|
274
|
+
return `url(${quote}${newUrl}${quote})`;
|
|
275
|
+
});
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
export const replaceRelativeUrlsInHtml = (html, baseUrl) => {
|
|
279
|
+
return replaceRelativeUrls(html, (url) => {
|
|
280
|
+
return new URL(url, baseUrl).href;
|
|
281
|
+
});
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
export const replaceRelativeUrlsInCssWithBase = (css, cssFileUrl) => {
|
|
285
|
+
return replaceRelativeUrlsInCss(css, (url) => {
|
|
286
|
+
return new URL(url, cssFileUrl).href;
|
|
287
|
+
});
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
export const unwrapBody = (code) => {
|
|
291
|
+
try {
|
|
292
|
+
return code.replace(/<\/?(html|body)[^>]*>/gi, '');
|
|
293
|
+
} catch (_error) {
|
|
294
|
+
return code;
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
export const transformBlockFile = (blockCode) => {
|
|
299
|
+
let transformedCode = '';
|
|
300
|
+
|
|
301
|
+
try {
|
|
302
|
+
transformedCode = babel.transformSync(blockCode, {
|
|
303
|
+
presets: [[presetReact, { pragma: 'wp.element.createElement' }]],
|
|
304
|
+
filename: 'block.js'
|
|
305
|
+
});
|
|
306
|
+
} catch (error) {
|
|
307
|
+
console.log(error);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return transformedCode;
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
export const getSnapApiUrl = () => {
|
|
314
|
+
return process.env.SNAPAPI_URL || 'https://api.snapapi.pics/v1/screenshot';
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
const uploadJobPackage = async ({ jobId, generatedFiles, assetFiles, previewArtifact }) => {
|
|
318
|
+
const allFiles = [];
|
|
319
|
+
const zipEntries = [];
|
|
320
|
+
let sourceIndex = 1;
|
|
321
|
+
let assetIndex = 1;
|
|
322
|
+
|
|
323
|
+
for (const [name, contents] of Object.entries(generatedFiles)) {
|
|
324
|
+
const body = typeof contents === 'string' ? Buffer.from(contents, 'utf8') : Buffer.from(contents || '');
|
|
325
|
+
const storageKey = `generated/${jobId}/${name}`;
|
|
326
|
+
const uploadResult = await uploadBufferToR2({
|
|
327
|
+
storageKey,
|
|
328
|
+
body,
|
|
329
|
+
contentType: inferContentType(name),
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
allFiles.push(
|
|
333
|
+
createFileRecord({
|
|
334
|
+
id: `file_${sourceIndex++}`,
|
|
335
|
+
name,
|
|
336
|
+
kind: 'source',
|
|
337
|
+
storageKey: uploadResult.storageKey,
|
|
338
|
+
size: uploadResult.size,
|
|
339
|
+
type: uploadResult.type,
|
|
340
|
+
url: uploadResult.url,
|
|
341
|
+
})
|
|
342
|
+
);
|
|
343
|
+
|
|
344
|
+
zipEntries.push({ name, body });
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
for (const assetFile of assetFiles) {
|
|
348
|
+
allFiles.push({
|
|
349
|
+
id: `file_${sourceIndex + assetIndex - 1}`,
|
|
350
|
+
name: assetFile.name,
|
|
351
|
+
type: assetFile.type,
|
|
352
|
+
size: assetFile.size,
|
|
353
|
+
path: assetFile.path,
|
|
354
|
+
url: assetFile.url,
|
|
355
|
+
kind: assetFile.kind || 'asset',
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
if (assetFile.buffer) {
|
|
359
|
+
zipEntries.push({
|
|
360
|
+
name: path.posix.relative(`generated/${jobId}`, assetFile.path.replace(/^\//, '')),
|
|
361
|
+
body: assetFile.buffer,
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
assetIndex++;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
if (previewArtifact) {
|
|
369
|
+
allFiles.push(previewArtifact.file);
|
|
370
|
+
zipEntries.push({
|
|
371
|
+
name: previewArtifact.file.name,
|
|
372
|
+
body: previewArtifact.buffer,
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
const zipBuffer = await zipEntriesToBuffer(zipEntries);
|
|
377
|
+
const bundleUpload = await uploadBufferToR2({
|
|
378
|
+
storageKey: `generated/${jobId}/output.zip`,
|
|
379
|
+
body: zipBuffer,
|
|
380
|
+
contentType: 'application/zip',
|
|
381
|
+
});
|
|
382
|
+
const bundleFile = createFileRecord({
|
|
383
|
+
id: 'file_bundle',
|
|
384
|
+
name: 'output.zip',
|
|
385
|
+
kind: 'bundle',
|
|
386
|
+
storageKey: bundleUpload.storageKey,
|
|
387
|
+
size: bundleUpload.size,
|
|
388
|
+
type: bundleUpload.type,
|
|
389
|
+
url: bundleUpload.url,
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
return {
|
|
393
|
+
jobId,
|
|
394
|
+
status: 'completed',
|
|
395
|
+
output: {
|
|
396
|
+
files: allFiles,
|
|
397
|
+
bundle: {
|
|
398
|
+
id: bundleFile.id,
|
|
399
|
+
name: bundleFile.name,
|
|
400
|
+
type: bundleFile.type,
|
|
401
|
+
size: bundleFile.size,
|
|
402
|
+
path: bundleFile.path,
|
|
403
|
+
url: bundleFile.url,
|
|
404
|
+
zipUrl: bundleUpload.url,
|
|
405
|
+
},
|
|
406
|
+
},
|
|
407
|
+
};
|
|
408
|
+
};
|
|
409
|
+
|
|
22
410
|
const block = async (
|
|
23
411
|
htmlContent,
|
|
24
|
-
|
|
25
|
-
name: 'My block',
|
|
26
|
-
prefix: 'wp',
|
|
27
|
-
category: 'common',
|
|
28
|
-
basePath: process.cwd(),
|
|
29
|
-
shouldSaveFiles: true,
|
|
30
|
-
generateIconPreview: false,
|
|
31
|
-
jsFiles: [],
|
|
32
|
-
cssFiles: [],
|
|
33
|
-
source: null,
|
|
34
|
-
}
|
|
412
|
+
rawOptions = {}
|
|
35
413
|
) => {
|
|
414
|
+
const options = normalizeBlockOptions(rawOptions);
|
|
36
415
|
const panels = [];
|
|
37
416
|
const styles = [];
|
|
38
417
|
const scripts = [];
|
|
39
418
|
const attributes = {};
|
|
40
419
|
const formVars = {};
|
|
420
|
+
const extractedAssets = [];
|
|
421
|
+
images.length = 0;
|
|
41
422
|
|
|
42
|
-
const { name, prefix, source } = options;
|
|
423
|
+
const { name, prefix, source, slug, registerCategoryIfMissing } = options;
|
|
424
|
+
const outputMode = options.outputMode;
|
|
425
|
+
const useR2Storage = options.uploadToR2;
|
|
426
|
+
const jobId = options.jobId || createJobId();
|
|
43
427
|
|
|
44
428
|
let js = '';
|
|
45
429
|
let css = '';
|
|
46
430
|
let phpEmailData = '';
|
|
47
431
|
let emailTemplate = '';
|
|
432
|
+
let previewArtifact = null;
|
|
433
|
+
const profiler = createProfiler(process.env.HTG_PROFILE === '1');
|
|
48
434
|
|
|
49
435
|
function hasTailwindCdnSource(jsFiles) {
|
|
50
436
|
const tailwindCdnRegex = /https:\/\/(cdn\.tailwindcss\.com(\?[^"'\s]*)?|cdn\.jsdelivr\.net\/npm\/@tailwindcss\/browser@4(\.\d+){0,2})/;
|
|
@@ -78,9 +464,7 @@ const block = async (
|
|
|
78
464
|
.replace(/^[^a-z]+/, '');
|
|
79
465
|
}
|
|
80
466
|
|
|
81
|
-
const replaceUnderscoresSpacesAndUppercaseLetters = (name = '') =>
|
|
82
|
-
return name.replace(new RegExp(/\W|_/, 'g'), '-').toLowerCase();
|
|
83
|
-
};
|
|
467
|
+
const replaceUnderscoresSpacesAndUppercaseLetters = (name = '') => slugifyBlockValue(name);
|
|
84
468
|
|
|
85
469
|
const saveFile = (fileName, contents, options) => {
|
|
86
470
|
try {
|
|
@@ -94,50 +478,6 @@ const block = async (
|
|
|
94
478
|
}
|
|
95
479
|
};
|
|
96
480
|
|
|
97
|
-
function replaceRelativeUrls(html, replacer) {
|
|
98
|
-
const urlAttributes = [
|
|
99
|
-
'src', 'href', 'action', 'srcset', 'poster', 'data', 'formaction'
|
|
100
|
-
];
|
|
101
|
-
|
|
102
|
-
const regex = new RegExp(
|
|
103
|
-
`\\b(${urlAttributes.join('|')}|data-[a-zA-Z0-9_-]+)\\s*=\\s*(['"])(?!https?:|//|mailto:|tel:|#)([^'"]+)\\2`,
|
|
104
|
-
'gi'
|
|
105
|
-
);
|
|
106
|
-
|
|
107
|
-
return html.replace(regex, (_match, attr, quote, url) => {
|
|
108
|
-
const newUrl = replacer(url);
|
|
109
|
-
return `${attr}=${quote}${newUrl}${quote}`;
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
function replaceRelativeUrlsInCss(css, replacer) {
|
|
115
|
-
const regex = /url\(\s*(['"]?)(.+?)\1\s*\)/gi;
|
|
116
|
-
|
|
117
|
-
return css.replace(regex, (match, quote, url) => {
|
|
118
|
-
if (/^(https?:|\/\/|data:|mailto:|tel:|#)/.test(url.trim())) {
|
|
119
|
-
return match;
|
|
120
|
-
}
|
|
121
|
-
const newUrl = replacer(url);
|
|
122
|
-
return `url(${quote}${newUrl}${quote})`;
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
function replaceRelativeUrlsInHtml(html, baseUrl) {
|
|
127
|
-
return replaceRelativeUrls(html, (url) => {
|
|
128
|
-
return new URL(url, baseUrl).href;
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
function replaceRelativeUrlsInCssWithBase(css, cssFileUrl) {
|
|
133
|
-
return replaceRelativeUrlsInCss(css, (url) => {
|
|
134
|
-
if (/^(https?:|\/\/|data:|mailto:|tel:|#)/.test(url.trim())) {
|
|
135
|
-
return url;
|
|
136
|
-
}
|
|
137
|
-
return new URL(url, cssFileUrl).href;
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
|
|
141
481
|
const parseRequirements = async (files, options) => {
|
|
142
482
|
const { source } = options;
|
|
143
483
|
let output = '';
|
|
@@ -173,9 +513,10 @@ const block = async (
|
|
|
173
513
|
return '';
|
|
174
514
|
};
|
|
175
515
|
|
|
176
|
-
const newName =
|
|
177
|
-
const
|
|
178
|
-
const
|
|
516
|
+
const newName = slug;
|
|
517
|
+
const normalizedNamespace = replaceUnderscoresSpacesAndUppercaseLetters(prefix);
|
|
518
|
+
const blockName = `${sanitizeAndReplaceLeadingNumbers(normalizedNamespace)}/${sanitizeAndReplaceLeadingNumbers(newName)}`;
|
|
519
|
+
const blockNameHandle = `${normalizedNamespace}-${newName}`;
|
|
179
520
|
|
|
180
521
|
const getPhp = (options) => {
|
|
181
522
|
const { name, prefix, jsFiles, cssFiles } = options;
|
|
@@ -386,40 +727,6 @@ const block = async (
|
|
|
386
727
|
`;
|
|
387
728
|
};
|
|
388
729
|
|
|
389
|
-
function preprocessSvgAttributes(code) {
|
|
390
|
-
return code.replace(/(<svg[\s\S]*?>[\s\S]*?<\/svg>)/gi, (svgBlock) => {
|
|
391
|
-
let processed = svgBlock.replace(/([a-zA-Z0-9]+)-([a-zA-Z0-9]+)=/g, (match, p1, p2) => {
|
|
392
|
-
const camel = p1 + p2.charAt(0).toUpperCase() + p2.slice(1);
|
|
393
|
-
return camel + '=';
|
|
394
|
-
});
|
|
395
|
-
return processed;
|
|
396
|
-
});
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
function unwrapBody(code) {
|
|
400
|
-
try {
|
|
401
|
-
return code.replace(/<\/?(html|body)[^>]*>/gi, '');
|
|
402
|
-
} catch (e) {
|
|
403
|
-
return code;
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
function transformBlockFile(blockCode) {
|
|
408
|
-
let test = '';
|
|
409
|
-
|
|
410
|
-
try {
|
|
411
|
-
test = babel.transformSync(blockCode, {
|
|
412
|
-
presets: [[presetReact, { pragma: 'wp.element.createElement' }]],
|
|
413
|
-
filename: 'block.js'
|
|
414
|
-
});
|
|
415
|
-
} catch (error) {
|
|
416
|
-
console.log(error);
|
|
417
|
-
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
return test;
|
|
421
|
-
}
|
|
422
|
-
|
|
423
730
|
const saveFiles = async (options) => {
|
|
424
731
|
const { cssFiles = [], jsFiles = [], shouldSaveFiles, name, prefix } = options;
|
|
425
732
|
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;
|
|
@@ -448,8 +755,9 @@ const block = async (
|
|
|
448
755
|
.replace(/<script[\s\S]*?<\/script>/gi, '')
|
|
449
756
|
.replace(/<style[\s\S]*?<\/style>/gi, '');
|
|
450
757
|
|
|
451
|
-
|
|
758
|
+
profiler.start('getBlock');
|
|
452
759
|
let blockCode = await getBlock(options);
|
|
760
|
+
profiler.end('getBlock');
|
|
453
761
|
|
|
454
762
|
// Ensure all <img> tags are self-closing for valid JSX
|
|
455
763
|
blockCode = blockCode.replace(/<img([^>/]*?)>/g, '<img$1 />');
|
|
@@ -458,34 +766,31 @@ const block = async (
|
|
|
458
766
|
const indexFile = getPhp(options);
|
|
459
767
|
let blockFile = '';
|
|
460
768
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
console.log(error);
|
|
467
|
-
}
|
|
769
|
+
profiler.start('transformBlockFile');
|
|
770
|
+
blockFile = transformBlockFile(blockCode).code
|
|
771
|
+
?.replace(/name: \"\{field.name\}\"/g, 'name: field.name')
|
|
772
|
+
?.replace(/key: \"\{index\}\"/g, 'key: index');
|
|
773
|
+
profiler.end('transformBlockFile');
|
|
468
774
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
}
|
|
775
|
+
const finalScriptsFile = `${scriptFile}\n\n${emailTemplate}`;
|
|
776
|
+
const remoteLoaderFile = '';
|
|
777
|
+
|
|
778
|
+
if (shouldSaveFiles && outputMode === 'legacy') {
|
|
779
|
+
saveFile('style.css', scopedCssFrontend, options);
|
|
780
|
+
saveFile('editor.css', editorStyleFile, options);
|
|
781
|
+
saveFile('scripts.js', finalScriptsFile, options);
|
|
782
|
+
saveFile('index.php', indexFile, options);
|
|
783
|
+
saveFile('block.js', blockFile, options);
|
|
784
|
+
saveFile('remote-loader.js', remoteLoaderFile, options);
|
|
481
785
|
}
|
|
482
786
|
|
|
483
787
|
return {
|
|
484
788
|
'style.css': scopedCssFrontend,
|
|
485
789
|
'editor.css': editorStyleFile,
|
|
486
|
-
'scripts.js':
|
|
790
|
+
'scripts.js': finalScriptsFile,
|
|
487
791
|
'index.php': indexFile,
|
|
488
792
|
'block.js': blockFile,
|
|
793
|
+
'remote-loader.js': remoteLoaderFile,
|
|
489
794
|
}
|
|
490
795
|
|
|
491
796
|
};
|
|
@@ -571,11 +876,20 @@ const block = async (
|
|
|
571
876
|
const loadHtml = async (options) => {
|
|
572
877
|
const { basePath, htmlContent } = options;
|
|
573
878
|
if (htmlContent) {
|
|
574
|
-
const
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
879
|
+
const extracted = await extractAssets(
|
|
880
|
+
htmlContent,
|
|
881
|
+
buildAssetExtractionOptions(basePath, {
|
|
882
|
+
uploadToR2: useR2Storage,
|
|
883
|
+
returnDetails: useR2Storage,
|
|
884
|
+
jobId,
|
|
885
|
+
r2Prefix: `generated/${jobId}/assets`,
|
|
886
|
+
})
|
|
887
|
+
);
|
|
888
|
+
const newHtml = typeof extracted === 'string' ? extracted : extracted.html;
|
|
889
|
+
|
|
890
|
+
if (typeof extracted !== 'string' && Array.isArray(extracted.assets)) {
|
|
891
|
+
extractedAssets.push(...extracted.assets);
|
|
892
|
+
}
|
|
579
893
|
|
|
580
894
|
return cheerio.load(newHtml, {
|
|
581
895
|
xmlMode: true,
|
|
@@ -639,7 +953,7 @@ const block = async (
|
|
|
639
953
|
};
|
|
640
954
|
const setImageAttribute = (properties) => {
|
|
641
955
|
const { imgTag, imgSrc, imgAlt, attribute, type, prefix } = properties;
|
|
642
|
-
const newPrefix =
|
|
956
|
+
const newPrefix = replaceUnderscoresSpacesAndUppercaseLetters(prefix);
|
|
643
957
|
const randomVariable = generateRandomVariableName(`${type}${newPrefix}`);
|
|
644
958
|
|
|
645
959
|
let imgSrcWithoutOrigin = imgSrc;
|
|
@@ -885,10 +1199,12 @@ const block = async (
|
|
|
885
1199
|
|
|
886
1200
|
content = `<div className="custom-block">${content}</div>`;
|
|
887
1201
|
|
|
888
|
-
|
|
1202
|
+
const processedImages = await processEditImages({
|
|
889
1203
|
...options,
|
|
890
1204
|
htmlContent: parseContent(content),
|
|
891
1205
|
});
|
|
1206
|
+
|
|
1207
|
+
return replaceSVGImages(processedImages);
|
|
892
1208
|
};
|
|
893
1209
|
|
|
894
1210
|
const createPanel = (values) => {
|
|
@@ -929,8 +1245,6 @@ const block = async (
|
|
|
929
1245
|
});
|
|
930
1246
|
// Replace with JSX template, preserving parent context
|
|
931
1247
|
result += getSvgTemplate(svgBlock, group1, '</svg>', randomSVGVariable);
|
|
932
|
-
} else {
|
|
933
|
-
result += svgBlock;
|
|
934
1248
|
}
|
|
935
1249
|
lastIndex = end;
|
|
936
1250
|
}
|
|
@@ -938,8 +1252,7 @@ const block = async (
|
|
|
938
1252
|
return result;
|
|
939
1253
|
};
|
|
940
1254
|
const getSvgPanelTemplate = (panel) => {
|
|
941
|
-
return
|
|
942
|
-
? `
|
|
1255
|
+
return `
|
|
943
1256
|
{ (
|
|
944
1257
|
<PanelBody title="${panel.title}">
|
|
945
1258
|
<PanelRow>
|
|
@@ -957,7 +1270,6 @@ const block = async (
|
|
|
957
1270
|
</PanelBody>
|
|
958
1271
|
)}
|
|
959
1272
|
`
|
|
960
|
-
: '';
|
|
961
1273
|
};
|
|
962
1274
|
|
|
963
1275
|
const getMediaPanelTemplate = (panel) => {
|
|
@@ -967,10 +1279,7 @@ const block = async (
|
|
|
967
1279
|
${panel.attributes[1]}: media.alt`
|
|
968
1280
|
: '';
|
|
969
1281
|
|
|
970
|
-
return
|
|
971
|
-
panel.attributes[0] &&
|
|
972
|
-
attributes[panel.attributes[0]]
|
|
973
|
-
? `
|
|
1282
|
+
return `
|
|
974
1283
|
<PanelBody title="${panel.title}">
|
|
975
1284
|
<PanelRow>
|
|
976
1285
|
<div>
|
|
@@ -989,8 +1298,7 @@ const block = async (
|
|
|
989
1298
|
</div>
|
|
990
1299
|
</PanelRow>
|
|
991
1300
|
</PanelBody>
|
|
992
|
-
|
|
993
|
-
: '';
|
|
1301
|
+
`;
|
|
994
1302
|
};
|
|
995
1303
|
|
|
996
1304
|
const getFormSettingsPanelTemplate = (panel) => {
|
|
@@ -1083,7 +1391,7 @@ const block = async (
|
|
|
1083
1391
|
formIdAttr,
|
|
1084
1392
|
] = panel.attributes;
|
|
1085
1393
|
|
|
1086
|
-
emailTemplate +=
|
|
1394
|
+
emailTemplate += getEmailSaveTemplate(formIdAttr);
|
|
1087
1395
|
phpEmailData += getPhpEmailData(formIdAttr, fromAttr, toAttr, subjectAttr, messageAttr);
|
|
1088
1396
|
|
|
1089
1397
|
return `
|
|
@@ -1280,32 +1588,9 @@ const block = async (
|
|
|
1280
1588
|
};
|
|
1281
1589
|
|
|
1282
1590
|
const buildSaveContent = (editContent) => {
|
|
1283
|
-
|
|
1284
|
-
let output = editContent.replace(
|
|
1285
|
-
/<RichText((.|\n)*?)value=\{(.*?)\}((.|\n)*?)>([\s\S]*?)<\/RichText>/gi,
|
|
1286
|
-
'<RichText.Content value={$3} />'
|
|
1287
|
-
);
|
|
1288
|
-
output = output.replace(
|
|
1289
|
-
/<RichText((.|\n)*?)value=\{(.*?)\}((.|\n)*?)\/>/gi,
|
|
1290
|
-
'<RichText.Content value={$3} />'
|
|
1291
|
-
);
|
|
1292
|
-
|
|
1293
|
-
// If a wrapper (e.g., span) contains only RichText.Content, keep the wrapper and its attributes
|
|
1294
|
-
output = output.replace(
|
|
1295
|
-
/(<([a-zA-Z0-9]+)([^>]*)>\s*)<RichText\.Content value=\{([^}]+)\} \/>(\s*<\/\2>)/gi,
|
|
1296
|
-
(match, openTag, tagName, attrs, value, closeTag) => {
|
|
1297
|
-
// Keep the wrapper and its attributes, insert RichText.Content inside
|
|
1298
|
-
return `${openTag}<RichText.Content value={${value}} />${closeTag}`;
|
|
1299
|
-
}
|
|
1300
|
-
);
|
|
1301
|
-
|
|
1591
|
+
let output = replaceRichTextComponents(editContent);
|
|
1302
1592
|
output = output.replaceAll('class=', 'className=');
|
|
1303
|
-
output = output
|
|
1304
|
-
/<MediaUpload\b[^>]*>([\s\S]*?(<img\b[^>]*>*\/>)[\s\S]*?)\/>/g,
|
|
1305
|
-
(_match, _attributes, img) => {
|
|
1306
|
-
return img.replace(/onClick={[^}]+}\s*/, '');
|
|
1307
|
-
}
|
|
1308
|
-
);
|
|
1593
|
+
output = replaceMediaUploadComponents(output, images);
|
|
1309
1594
|
return output;
|
|
1310
1595
|
};
|
|
1311
1596
|
|
|
@@ -1329,45 +1614,16 @@ const block = async (
|
|
|
1329
1614
|
)
|
|
1330
1615
|
: undefined;
|
|
1331
1616
|
|
|
1332
|
-
htmlContent = unwrapAnchor(htmlContent);
|
|
1333
|
-
|
|
1334
1617
|
return {
|
|
1335
1618
|
...options,
|
|
1336
1619
|
htmlContent,
|
|
1337
1620
|
};
|
|
1338
1621
|
};
|
|
1339
1622
|
|
|
1340
|
-
const unwrapAnchor = (htmlContent) => {
|
|
1341
|
-
return htmlContent.replace(
|
|
1342
|
-
/<span([^>]*)>\s*<a([^>]*)>(.*?)<\/a>\s*<\/span>/gi,
|
|
1343
|
-
(_, spanAttrs, anchorAttrs, content) => {
|
|
1344
|
-
const allAttrs = {};
|
|
1345
|
-
const attrRegex = /(\S+)=["'](.*?)["']/g;
|
|
1346
|
-
|
|
1347
|
-
let match;
|
|
1348
|
-
while ((match = attrRegex.exec(spanAttrs)) !== null) {
|
|
1349
|
-
allAttrs[match[1]] = match[2];
|
|
1350
|
-
}
|
|
1351
|
-
|
|
1352
|
-
while ((match = attrRegex.exec(anchorAttrs)) !== null) {
|
|
1353
|
-
allAttrs[match[1]] = match[2];
|
|
1354
|
-
}
|
|
1355
|
-
|
|
1356
|
-
return `<a ${Object.entries(allAttrs)
|
|
1357
|
-
.map(([key, value]) => `${key}="${value}"`)
|
|
1358
|
-
.join(' ')}>${content}</a>`;
|
|
1359
|
-
}
|
|
1360
|
-
);
|
|
1361
|
-
};
|
|
1362
|
-
|
|
1363
1623
|
const getComponentAttributes = () => {
|
|
1364
1624
|
return Object.entries(attributes)
|
|
1365
1625
|
.map(([key, value]) => {
|
|
1366
|
-
|
|
1367
|
-
return `${key}: { ${Object.entries(value).map(([k, v]) => `${k}: \`${v}\``).join(', ')} }`;
|
|
1368
|
-
} else {
|
|
1369
|
-
return `${key}:\`${value}\``;
|
|
1370
|
-
}
|
|
1626
|
+
return `${key}: { ${Object.entries(value).map(([k, v]) => `${k}: \`${v}\``).join(', ')} }`;
|
|
1371
1627
|
})
|
|
1372
1628
|
.join(',\n');
|
|
1373
1629
|
};
|
|
@@ -1376,11 +1632,14 @@ const block = async (
|
|
|
1376
1632
|
let { htmlContent } = options;
|
|
1377
1633
|
|
|
1378
1634
|
if (htmlContent) {
|
|
1635
|
+
profiler.start('getEdit');
|
|
1379
1636
|
options.htmlContent = unwrapBody(htmlContent);
|
|
1380
1637
|
const postProcessLinks = processLinks(options);
|
|
1381
1638
|
const postGetEditJsx = await getEditJsxContent(postProcessLinks);
|
|
1382
1639
|
const preConvert = await postGetEditJsx.replace(/<\/br>/g, '<br/>').replace(/<\/hr>/g, '<hr/>')
|
|
1383
|
-
|
|
1640
|
+
const converted = convert(preConvert);
|
|
1641
|
+
profiler.end('getEdit');
|
|
1642
|
+
return converted;
|
|
1384
1643
|
}
|
|
1385
1644
|
|
|
1386
1645
|
return '';
|
|
@@ -1399,13 +1658,54 @@ const block = async (
|
|
|
1399
1658
|
} = settings;
|
|
1400
1659
|
|
|
1401
1660
|
const iconPreview = generateIconPreview ? `(<img src={vars.url + 'preview.jpeg'} />)` : "'shield'";
|
|
1661
|
+
const categoryRegistration = registerCategoryIfMissing
|
|
1662
|
+
? `
|
|
1663
|
+
(function ensureBlockCategory() {
|
|
1664
|
+
if (
|
|
1665
|
+
typeof wp === 'undefined' ||
|
|
1666
|
+
!wp.blocks ||
|
|
1667
|
+
!wp.blocks.setCategories ||
|
|
1668
|
+
!wp.blocks.store ||
|
|
1669
|
+
!wp.data ||
|
|
1670
|
+
!wp.data.select
|
|
1671
|
+
) {
|
|
1672
|
+
return;
|
|
1673
|
+
}
|
|
1674
|
+
|
|
1675
|
+
const categorySelector = wp.data.select(wp.blocks.store);
|
|
1676
|
+
const existingCategories =
|
|
1677
|
+
categorySelector && typeof categorySelector.getCategories === 'function'
|
|
1678
|
+
? categorySelector.getCategories()
|
|
1679
|
+
: [];
|
|
1680
|
+
|
|
1681
|
+
if (existingCategories.some((item) => item && item.slug === ${JSON.stringify(category)})) {
|
|
1682
|
+
return;
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
wp.blocks.setCategories([
|
|
1686
|
+
...existingCategories,
|
|
1687
|
+
{
|
|
1688
|
+
slug: ${JSON.stringify(category)},
|
|
1689
|
+
title: ${JSON.stringify(formatCategoryLabel(category) || category)},
|
|
1690
|
+
},
|
|
1691
|
+
]);
|
|
1692
|
+
})();
|
|
1693
|
+
`
|
|
1694
|
+
: '';
|
|
1695
|
+
profiler.start('getBlock:getEdit');
|
|
1402
1696
|
const edit = await getEdit(settings);
|
|
1697
|
+
profiler.end('getBlock:getEdit');
|
|
1698
|
+
profiler.start('getBlock:buildSaveContent');
|
|
1403
1699
|
const save = buildSaveContent(edit);
|
|
1700
|
+
profiler.end('getBlock:buildSaveContent');
|
|
1701
|
+
profiler.start('getBlock:createPanels');
|
|
1404
1702
|
const blockPanels = createPanels();
|
|
1703
|
+
profiler.end('getBlock:createPanels');
|
|
1405
1704
|
|
|
1406
1705
|
const output = `
|
|
1407
1706
|
(function () {
|
|
1408
1707
|
${imports}
|
|
1708
|
+
${categoryRegistration}
|
|
1409
1709
|
|
|
1410
1710
|
registerBlockType('${blockName}', {
|
|
1411
1711
|
title: '${name}',
|
|
@@ -1441,7 +1741,7 @@ const block = async (
|
|
|
1441
1741
|
const setupVariables = async (htmlContent, options) => {
|
|
1442
1742
|
|
|
1443
1743
|
const styleRegex = /<style[^>]*>([\s\S]*?)<\/style>/gi;
|
|
1444
|
-
const linkRegex = /<link\b[^>]*
|
|
1744
|
+
const linkRegex = /<link\b[^>]*rel=["']stylesheet["'][^>]*href=["']([^"']+\.css(?:\?[^"']*)?)["'][^>]*>/gi;
|
|
1445
1745
|
|
|
1446
1746
|
let match;
|
|
1447
1747
|
|
|
@@ -1454,7 +1754,13 @@ const block = async (
|
|
|
1454
1754
|
while ((match = linkRegex.exec(htmlContent)) !== null) {
|
|
1455
1755
|
const url = match[1];
|
|
1456
1756
|
const fetchCssPromise = fetch(url)
|
|
1457
|
-
.then((response) =>
|
|
1757
|
+
.then((response) => {
|
|
1758
|
+
if (!response.ok) {
|
|
1759
|
+
throw new Error(`HTTP error! Status: ${response.status}`);
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1762
|
+
return response.text();
|
|
1763
|
+
})
|
|
1458
1764
|
.then((css) => styles.push({ type: 'external', content: css }))
|
|
1459
1765
|
.catch(() => console.warn(`Failed to fetch: ${url}`));
|
|
1460
1766
|
fetchCssPromises.push(fetchCssPromise);
|
|
@@ -1468,25 +1774,24 @@ const block = async (
|
|
|
1468
1774
|
return `${style.content}`;
|
|
1469
1775
|
}).join('\n');
|
|
1470
1776
|
|
|
1471
|
-
const scriptRegex = /<script[^>]*>([\s\S]*?)<\/script>/gi;
|
|
1777
|
+
const scriptRegex = /<script(?![^>]*\bsrc=)[^>]*>([\s\S]*?)<\/script>/gi;
|
|
1472
1778
|
const scriptSrcRegex =
|
|
1473
1779
|
/<script\s+[^>]*src=["']([^"']+)["'][^>]*>\s*<\/script>/gi;
|
|
1474
1780
|
|
|
1475
1781
|
let jsMatch;
|
|
1476
1782
|
|
|
1477
|
-
htmlContent = htmlContent.replace(scriptRegex, (_fullMatch, jsContent) => {
|
|
1478
|
-
if (jsContent.trim()) {
|
|
1479
|
-
scripts.push({ type: 'inline', content: jsContent });
|
|
1480
|
-
}
|
|
1481
|
-
return '';
|
|
1482
|
-
});
|
|
1483
|
-
|
|
1484
1783
|
const fetchJsPromises = [];
|
|
1485
1784
|
|
|
1486
1785
|
while ((jsMatch = scriptSrcRegex.exec(htmlContent)) !== null) {
|
|
1487
1786
|
const url = jsMatch[1];
|
|
1488
1787
|
const fetchJsPromise = fetch(url)
|
|
1489
|
-
.then((response) =>
|
|
1788
|
+
.then((response) => {
|
|
1789
|
+
if (!response.ok) {
|
|
1790
|
+
throw new Error(`HTTP error! Status: ${response.status}`);
|
|
1791
|
+
}
|
|
1792
|
+
|
|
1793
|
+
return response.text();
|
|
1794
|
+
})
|
|
1490
1795
|
.then((js) => scripts.push({ type: 'external', content: js }))
|
|
1491
1796
|
.catch(() => console.warn(`Failed to fetch script: ${url}`));
|
|
1492
1797
|
fetchJsPromises.push(fetchJsPromise);
|
|
@@ -1494,6 +1799,13 @@ const block = async (
|
|
|
1494
1799
|
|
|
1495
1800
|
htmlContent = htmlContent.replace(scriptSrcRegex, '');
|
|
1496
1801
|
|
|
1802
|
+
htmlContent = htmlContent.replace(scriptRegex, (_fullMatch, jsContent) => {
|
|
1803
|
+
if (jsContent.trim()) {
|
|
1804
|
+
scripts.push({ type: 'inline', content: jsContent });
|
|
1805
|
+
}
|
|
1806
|
+
return '';
|
|
1807
|
+
});
|
|
1808
|
+
|
|
1497
1809
|
await Promise.all(fetchJsPromises);
|
|
1498
1810
|
|
|
1499
1811
|
js += scripts.map((script) => script.content).join('\n');
|
|
@@ -1503,9 +1815,10 @@ const block = async (
|
|
|
1503
1815
|
cssFiles = [],
|
|
1504
1816
|
jsFiles = [],
|
|
1505
1817
|
name = 'My block',
|
|
1818
|
+
slug = replaceUnderscoresSpacesAndUppercaseLetters(name),
|
|
1506
1819
|
} = options;
|
|
1507
1820
|
|
|
1508
|
-
const newDir = path.join(basePath,
|
|
1821
|
+
const newDir = path.join(basePath, slug);
|
|
1509
1822
|
|
|
1510
1823
|
const $ = cheerio.load(htmlContent, {
|
|
1511
1824
|
xmlMode: true,
|
|
@@ -1516,22 +1829,17 @@ const block = async (
|
|
|
1516
1829
|
|
|
1517
1830
|
htmlContent = $('body').html();
|
|
1518
1831
|
|
|
1519
|
-
options.
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
try {
|
|
1832
|
+
if (outputMode === 'legacy' && options.shouldSaveFiles) {
|
|
1523
1833
|
fs.mkdirSync(newDir, { recursive: true });
|
|
1524
|
-
|
|
1525
|
-
return {
|
|
1526
|
-
...options,
|
|
1527
|
-
jsFiles,
|
|
1528
|
-
cssFiles,
|
|
1529
|
-
htmlContent,
|
|
1530
|
-
basePath: newDir,
|
|
1531
|
-
};
|
|
1532
|
-
} catch (error) {
|
|
1533
|
-
logError(error);
|
|
1534
1834
|
}
|
|
1835
|
+
|
|
1836
|
+
return {
|
|
1837
|
+
...options,
|
|
1838
|
+
jsFiles,
|
|
1839
|
+
cssFiles,
|
|
1840
|
+
htmlContent,
|
|
1841
|
+
basePath: newDir,
|
|
1842
|
+
};
|
|
1535
1843
|
};
|
|
1536
1844
|
|
|
1537
1845
|
if (source) {
|
|
@@ -1542,14 +1850,14 @@ const block = async (
|
|
|
1542
1850
|
|
|
1543
1851
|
|
|
1544
1852
|
// Screenshot generation using SnapAPI
|
|
1545
|
-
dotenv.config();
|
|
1853
|
+
dotenv.config({ quiet: true });
|
|
1546
1854
|
if (options.generateIconPreview && options.source) {
|
|
1547
1855
|
try {
|
|
1548
1856
|
const snapApiKey = process.env.SNAPAPI_KEY;
|
|
1549
1857
|
if (!snapApiKey) {
|
|
1550
1858
|
throw new Error('SNAPAPI_KEY is not set in environment variables.');
|
|
1551
1859
|
}
|
|
1552
|
-
const snapApiUrl =
|
|
1860
|
+
const snapApiUrl = getSnapApiUrl();
|
|
1553
1861
|
const snapApiBody = {
|
|
1554
1862
|
url: options.source,
|
|
1555
1863
|
fullPage: true,
|
|
@@ -1568,15 +1876,53 @@ const block = async (
|
|
|
1568
1876
|
if (!response.ok) {
|
|
1569
1877
|
throw new Error(`SnapAPI error: ${response.status}`);
|
|
1570
1878
|
}
|
|
1571
|
-
const previewPath = path.join(options.basePath, replaceUnderscoresSpacesAndUppercaseLetters(options.name), 'preview.jpeg');
|
|
1572
1879
|
const buffer = Buffer.from(await response.arrayBuffer());
|
|
1573
|
-
|
|
1880
|
+
|
|
1881
|
+
if (outputMode === 'legacy' && options.shouldSaveFiles) {
|
|
1882
|
+
const previewPath = path.join(options.basePath, options.slug, 'preview.jpeg');
|
|
1883
|
+
fs.writeFileSync(previewPath, buffer);
|
|
1884
|
+
} else if (useR2Storage) {
|
|
1885
|
+
const uploadResult = await uploadBufferToR2({
|
|
1886
|
+
storageKey: `generated/${jobId}/preview.jpeg`,
|
|
1887
|
+
body: buffer,
|
|
1888
|
+
contentType: 'image/jpeg',
|
|
1889
|
+
});
|
|
1890
|
+
|
|
1891
|
+
previewArtifact = {
|
|
1892
|
+
buffer,
|
|
1893
|
+
file: createFileRecord({
|
|
1894
|
+
id: `file_preview`,
|
|
1895
|
+
name: 'preview.jpeg',
|
|
1896
|
+
kind: 'asset',
|
|
1897
|
+
storageKey: uploadResult.storageKey,
|
|
1898
|
+
size: uploadResult.size,
|
|
1899
|
+
type: uploadResult.type,
|
|
1900
|
+
url: uploadResult.url,
|
|
1901
|
+
}),
|
|
1902
|
+
};
|
|
1903
|
+
}
|
|
1574
1904
|
} catch (error) {
|
|
1575
1905
|
console.log(`There was an error generating preview with SnapAPI. ${error.message}`);
|
|
1576
1906
|
}
|
|
1577
1907
|
}
|
|
1578
1908
|
|
|
1579
|
-
|
|
1909
|
+
profiler.start('setupVariables');
|
|
1910
|
+
const preparedOptions = await setupVariables(htmlContent, options);
|
|
1911
|
+
profiler.end('setupVariables');
|
|
1912
|
+
profiler.start('saveFiles');
|
|
1913
|
+
const result = await saveFiles(preparedOptions);
|
|
1914
|
+
profiler.end('saveFiles');
|
|
1915
|
+
|
|
1916
|
+
if (outputMode === 'legacy') {
|
|
1917
|
+
return result;
|
|
1918
|
+
}
|
|
1919
|
+
|
|
1920
|
+
return uploadJobPackage({
|
|
1921
|
+
jobId,
|
|
1922
|
+
generatedFiles: result,
|
|
1923
|
+
assetFiles: extractedAssets,
|
|
1924
|
+
previewArtifact,
|
|
1925
|
+
});
|
|
1580
1926
|
};
|
|
1581
1927
|
|
|
1582
|
-
export default block;
|
|
1928
|
+
export default block;
|