@elementor/editor-canvas 4.1.0-736 → 4.1.0-738
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/dist/index.js +135 -47
- package/dist/index.mjs +93 -5
- package/package.json +19 -18
- package/src/hooks/use-style-items.ts +8 -0
- package/src/init-settings-transformers.ts +2 -0
- package/src/transformers/shared/__tests__/svg-src-transformer.test.ts +184 -0
- package/src/transformers/shared/svg-src-transformer.ts +87 -0
- package/src/utils/__tests__/pregenerated-links-removal.test.ts +208 -0
- package/src/utils/pregenerated-links-removal.ts +35 -0
package/dist/index.js
CHANGED
|
@@ -586,7 +586,7 @@ function usePortalContainer() {
|
|
|
586
586
|
|
|
587
587
|
// src/components/style-renderer.tsx
|
|
588
588
|
var React4 = __toESM(require("react"));
|
|
589
|
-
var
|
|
589
|
+
var import_editor_v1_adapters9 = require("@elementor/editor-v1-adapters");
|
|
590
590
|
var import_ui3 = require("@elementor/ui");
|
|
591
591
|
|
|
592
592
|
// src/hooks/use-documents-css-links.ts
|
|
@@ -641,7 +641,7 @@ var import_react10 = require("react");
|
|
|
641
641
|
var import_editor_responsive2 = require("@elementor/editor-responsive");
|
|
642
642
|
var import_editor_styles4 = require("@elementor/editor-styles");
|
|
643
643
|
var import_editor_styles_repository2 = require("@elementor/editor-styles-repository");
|
|
644
|
-
var
|
|
644
|
+
var import_editor_v1_adapters8 = require("@elementor/editor-v1-adapters");
|
|
645
645
|
|
|
646
646
|
// src/utils/abort-previous-runs.ts
|
|
647
647
|
function abortPreviousRuns(cb) {
|
|
@@ -655,6 +655,30 @@ function abortPreviousRuns(cb) {
|
|
|
655
655
|
};
|
|
656
656
|
}
|
|
657
657
|
|
|
658
|
+
// src/utils/pregenerated-links-removal.ts
|
|
659
|
+
var import_editor_v1_adapters6 = require("@elementor/editor-v1-adapters");
|
|
660
|
+
var removedProviderKeys = /* @__PURE__ */ new Set();
|
|
661
|
+
function removeProviderPregeneratedLinks(providerKey, removePregeneratedLink) {
|
|
662
|
+
if (removedProviderKeys.has(providerKey)) {
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
const iframeDocument = (0, import_editor_v1_adapters6.getCanvasIframeDocument)();
|
|
666
|
+
if (!iframeDocument) {
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
669
|
+
const links = iframeDocument.head.querySelectorAll('link[rel="stylesheet"]');
|
|
670
|
+
links.forEach((link) => {
|
|
671
|
+
const { id, href, media } = link;
|
|
672
|
+
if (removePregeneratedLink({ id, href, media })) {
|
|
673
|
+
link.remove();
|
|
674
|
+
}
|
|
675
|
+
});
|
|
676
|
+
removedProviderKeys.add(providerKey);
|
|
677
|
+
}
|
|
678
|
+
function resetRemovedProviders() {
|
|
679
|
+
removedProviderKeys.clear();
|
|
680
|
+
}
|
|
681
|
+
|
|
658
682
|
// src/utils/signalized-process.ts
|
|
659
683
|
function signalizedProcess(signal, steps = []) {
|
|
660
684
|
return {
|
|
@@ -677,7 +701,7 @@ function signalizedProcess(signal, steps = []) {
|
|
|
677
701
|
// src/hooks/use-style-prop-resolver.ts
|
|
678
702
|
var import_react8 = require("react");
|
|
679
703
|
var import_editor_styles2 = require("@elementor/editor-styles");
|
|
680
|
-
var
|
|
704
|
+
var import_editor_v1_adapters7 = require("@elementor/editor-v1-adapters");
|
|
681
705
|
|
|
682
706
|
// src/renderers/create-props-resolver.ts
|
|
683
707
|
var import_editor_props2 = require("@elementor/editor-props");
|
|
@@ -811,7 +835,7 @@ function useStylePropResolver() {
|
|
|
811
835
|
if (key !== "font-family" || typeof value !== "string") {
|
|
812
836
|
return;
|
|
813
837
|
}
|
|
814
|
-
(0,
|
|
838
|
+
(0, import_editor_v1_adapters7.enqueueFont)(value);
|
|
815
839
|
}
|
|
816
840
|
});
|
|
817
841
|
}, []);
|
|
@@ -970,7 +994,8 @@ function useStyleItems() {
|
|
|
970
994
|
};
|
|
971
995
|
}, [providerAndSubscribers]);
|
|
972
996
|
useOnMount(() => {
|
|
973
|
-
(0,
|
|
997
|
+
(0, import_editor_v1_adapters8.registerDataHook)("after", "editor/documents/attach-preview", async () => {
|
|
998
|
+
resetRemovedProviders();
|
|
974
999
|
const promises = providerAndSubscribers.map(async ({ subscriber }) => subscriber());
|
|
975
1000
|
await Promise.all(promises);
|
|
976
1001
|
});
|
|
@@ -1004,6 +1029,9 @@ function createProviderSubscriber2({ provider, renderStyles, setStyleItems, cach
|
|
|
1004
1029
|
(abortController, previous, current) => signalizedProcess(abortController.signal).then((_, signal) => {
|
|
1005
1030
|
const hasDiffInfo = current !== void 0 && previous !== void 0;
|
|
1006
1031
|
const hasCache = cache.orderedIds.length > 0;
|
|
1032
|
+
if (hasCache && provider.isPregeneratedLink) {
|
|
1033
|
+
removeProviderPregeneratedLinks(provider.getKey(), provider.isPregeneratedLink);
|
|
1034
|
+
}
|
|
1007
1035
|
if (hasDiffInfo && hasCache) {
|
|
1008
1036
|
return updateItems(previous, current, signal);
|
|
1009
1037
|
}
|
|
@@ -1117,7 +1145,7 @@ function StyleRenderer() {
|
|
|
1117
1145
|
return /* @__PURE__ */ React4.createElement(import_ui3.Portal, { container }, filterUniqueStyleDefinitions(styleItems).map((item) => /* @__PURE__ */ React4.createElement("style", { key: `${item.id}-${item.breakpoint}-${item.state ?? "normal"}` }, item.value)), linksAttrs.map((attrs) => /* @__PURE__ */ React4.createElement("link", { ...attrs, key: attrs.id })));
|
|
1118
1146
|
}
|
|
1119
1147
|
function usePortalContainer2() {
|
|
1120
|
-
return (0,
|
|
1148
|
+
return (0, import_editor_v1_adapters9.__privateUseListenTo)((0, import_editor_v1_adapters9.commandEndEvent)("editor/documents/attach-preview"), () => (0, import_editor_v1_adapters9.getCanvasIframeDocument)()?.head);
|
|
1121
1149
|
}
|
|
1122
1150
|
function filterUniqueStyleDefinitions(styleItems) {
|
|
1123
1151
|
const seen = /* @__PURE__ */ new Map();
|
|
@@ -1140,7 +1168,7 @@ function filterUniqueStyleDefinitions(styleItems) {
|
|
|
1140
1168
|
|
|
1141
1169
|
// src/form-structure/enforce-form-ancestor-commands.ts
|
|
1142
1170
|
var import_editor_notifications = require("@elementor/editor-notifications");
|
|
1143
|
-
var
|
|
1171
|
+
var import_editor_v1_adapters10 = require("@elementor/editor-v1-adapters");
|
|
1144
1172
|
var import_i18n = require("@wordpress/i18n");
|
|
1145
1173
|
|
|
1146
1174
|
// src/form-structure/utils.ts
|
|
@@ -1200,15 +1228,15 @@ var FORM_FIELDS_OUTSIDE_ALERT = {
|
|
|
1200
1228
|
id: "form-fields-outside-form-blocked"
|
|
1201
1229
|
};
|
|
1202
1230
|
function initFormAncestorEnforcement() {
|
|
1203
|
-
(0,
|
|
1231
|
+
(0, import_editor_v1_adapters10.blockCommand)({
|
|
1204
1232
|
command: "document/elements/create",
|
|
1205
1233
|
condition: blockFormFieldCreate
|
|
1206
1234
|
});
|
|
1207
|
-
(0,
|
|
1235
|
+
(0, import_editor_v1_adapters10.blockCommand)({
|
|
1208
1236
|
command: "document/elements/move",
|
|
1209
1237
|
condition: blockFormFieldMove
|
|
1210
1238
|
});
|
|
1211
|
-
(0,
|
|
1239
|
+
(0, import_editor_v1_adapters10.blockCommand)({
|
|
1212
1240
|
command: "document/elements/paste",
|
|
1213
1241
|
condition: blockFormFieldPaste
|
|
1214
1242
|
});
|
|
@@ -1257,7 +1285,7 @@ function handleBlockedFormField() {
|
|
|
1257
1285
|
|
|
1258
1286
|
// src/form-structure/prevent-form-nesting-commands.ts
|
|
1259
1287
|
var import_editor_notifications2 = require("@elementor/editor-notifications");
|
|
1260
|
-
var
|
|
1288
|
+
var import_editor_v1_adapters11 = require("@elementor/editor-v1-adapters");
|
|
1261
1289
|
var import_i18n2 = require("@wordpress/i18n");
|
|
1262
1290
|
var FORM_NESTING_ALERT = {
|
|
1263
1291
|
type: "default",
|
|
@@ -1265,15 +1293,15 @@ var FORM_NESTING_ALERT = {
|
|
|
1265
1293
|
id: "form-nesting-blocked"
|
|
1266
1294
|
};
|
|
1267
1295
|
function initFormNestingPrevention() {
|
|
1268
|
-
(0,
|
|
1296
|
+
(0, import_editor_v1_adapters11.blockCommand)({
|
|
1269
1297
|
command: "document/elements/create",
|
|
1270
1298
|
condition: blockFormCreate
|
|
1271
1299
|
});
|
|
1272
|
-
(0,
|
|
1300
|
+
(0, import_editor_v1_adapters11.blockCommand)({
|
|
1273
1301
|
command: "document/elements/move",
|
|
1274
1302
|
condition: blockFormMove
|
|
1275
1303
|
});
|
|
1276
|
-
(0,
|
|
1304
|
+
(0, import_editor_v1_adapters11.blockCommand)({
|
|
1277
1305
|
command: "document/elements/paste",
|
|
1278
1306
|
condition: blockFormPaste
|
|
1279
1307
|
});
|
|
@@ -1425,14 +1453,74 @@ var plainTransformer = createTransformer((value) => {
|
|
|
1425
1453
|
return value;
|
|
1426
1454
|
});
|
|
1427
1455
|
|
|
1428
|
-
// src/transformers/shared/
|
|
1456
|
+
// src/transformers/shared/svg-src-transformer.ts
|
|
1457
|
+
var import_dompurify = __toESM(require("dompurify"));
|
|
1429
1458
|
var import_wp_media2 = require("@elementor/wp-media");
|
|
1459
|
+
var SVG_INLINE_STYLES = "width: 100%; height: 100%; overflow: unset;";
|
|
1460
|
+
function processSvgContent(svgText) {
|
|
1461
|
+
const sanitized = import_dompurify.default.sanitize(svgText, {
|
|
1462
|
+
USE_PROFILES: { svg: true, svgFilters: true }
|
|
1463
|
+
});
|
|
1464
|
+
const parser = new DOMParser();
|
|
1465
|
+
const doc = parser.parseFromString(sanitized, "image/svg+xml");
|
|
1466
|
+
const svgElement = doc.querySelector("svg");
|
|
1467
|
+
if (!svgElement) {
|
|
1468
|
+
return null;
|
|
1469
|
+
}
|
|
1470
|
+
svgElement.setAttribute("fill", "currentColor");
|
|
1471
|
+
const existingStyle = svgElement.getAttribute("style") ?? "";
|
|
1472
|
+
const trimmed = existingStyle.trim();
|
|
1473
|
+
const merged = trimmed ? `${trimmed.replace(/;$/, "")}; ${SVG_INLINE_STYLES}` : SVG_INLINE_STYLES;
|
|
1474
|
+
svgElement.setAttribute("style", merged);
|
|
1475
|
+
return svgElement.outerHTML;
|
|
1476
|
+
}
|
|
1477
|
+
async function fetchSvgContent(url, signal) {
|
|
1478
|
+
try {
|
|
1479
|
+
const response = await fetch(url, { signal });
|
|
1480
|
+
if (!response.ok) {
|
|
1481
|
+
return null;
|
|
1482
|
+
}
|
|
1483
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
1484
|
+
const isSvg = contentType.includes("svg") || contentType.includes("xml") || url.endsWith(".svg");
|
|
1485
|
+
if (!isSvg) {
|
|
1486
|
+
return null;
|
|
1487
|
+
}
|
|
1488
|
+
return await response.text();
|
|
1489
|
+
} catch {
|
|
1490
|
+
return null;
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
function resolveSvgSrcId(id) {
|
|
1494
|
+
if (typeof id !== "number" || id <= 0) {
|
|
1495
|
+
return null;
|
|
1496
|
+
}
|
|
1497
|
+
return id;
|
|
1498
|
+
}
|
|
1499
|
+
var svgSrcTransformer = createTransformer(async (value, { signal }) => {
|
|
1500
|
+
const id = resolveSvgSrcId(value.id);
|
|
1501
|
+
const urlFromValue = typeof value.url === "string" ? value.url : null;
|
|
1502
|
+
let url = urlFromValue;
|
|
1503
|
+
if (id && !urlFromValue) {
|
|
1504
|
+
const attachment = await (0, import_wp_media2.getMediaAttachment)({ id });
|
|
1505
|
+
url = attachment?.url ?? null;
|
|
1506
|
+
}
|
|
1507
|
+
const resolvedUrl = typeof url === "string" ? url : null;
|
|
1508
|
+
if (!resolvedUrl) {
|
|
1509
|
+
return { html: null, url: null };
|
|
1510
|
+
}
|
|
1511
|
+
const svgText = await fetchSvgContent(resolvedUrl, signal);
|
|
1512
|
+
const html = svgText ? processSvgContent(svgText) : null;
|
|
1513
|
+
return { html, url: resolvedUrl };
|
|
1514
|
+
});
|
|
1515
|
+
|
|
1516
|
+
// src/transformers/shared/video-src-transformer.ts
|
|
1517
|
+
var import_wp_media3 = require("@elementor/wp-media");
|
|
1430
1518
|
var videoSrcTransformer = createTransformer(async (value) => {
|
|
1431
1519
|
const { id, url } = value;
|
|
1432
1520
|
if (!id) {
|
|
1433
1521
|
return { id: null, url };
|
|
1434
1522
|
}
|
|
1435
|
-
const attachment = await (0,
|
|
1523
|
+
const attachment = await (0, import_wp_media3.getMediaAttachment)({ id });
|
|
1436
1524
|
return {
|
|
1437
1525
|
id,
|
|
1438
1526
|
url: attachment?.url ?? url
|
|
@@ -1441,7 +1529,7 @@ var videoSrcTransformer = createTransformer(async (value) => {
|
|
|
1441
1529
|
|
|
1442
1530
|
// src/init-settings-transformers.ts
|
|
1443
1531
|
function initSettingsTransformers() {
|
|
1444
|
-
settingsTransformersRegistry.register("classes", createClassesTransformer()).register("link", linkTransformer).register("query", queryTransformer).register("image", imageTransformer).register("image-src", imageSrcTransformer).register("video-src", videoSrcTransformer).register("attributes", attributesTransformer).register("date-time", dateTimeTransformer).register("html-v2", htmlV2Transformer).register("html-v3", htmlV3Transformer).registerFallback(plainTransformer);
|
|
1532
|
+
settingsTransformersRegistry.register("classes", createClassesTransformer()).register("link", linkTransformer).register("query", queryTransformer).register("image", imageTransformer).register("image-src", imageSrcTransformer).register("svg-src", svgSrcTransformer).register("video-src", videoSrcTransformer).register("attributes", attributesTransformer).register("date-time", dateTimeTransformer).register("html-v2", htmlV2Transformer).register("html-v3", htmlV3Transformer).registerFallback(plainTransformer);
|
|
1445
1533
|
}
|
|
1446
1534
|
|
|
1447
1535
|
// src/transformers/styles/background-color-overlay-transformer.ts
|
|
@@ -1778,7 +1866,7 @@ function initStyleTransformers() {
|
|
|
1778
1866
|
|
|
1779
1867
|
// src/legacy/init-legacy-views.ts
|
|
1780
1868
|
var import_editor_elements6 = require("@elementor/editor-elements");
|
|
1781
|
-
var
|
|
1869
|
+
var import_editor_v1_adapters13 = require("@elementor/editor-v1-adapters");
|
|
1782
1870
|
|
|
1783
1871
|
// src/renderers/create-dom-renderer.ts
|
|
1784
1872
|
var import_twing = require("@elementor/twing");
|
|
@@ -2326,7 +2414,7 @@ var React6 = __toESM(require("react"));
|
|
|
2326
2414
|
var import_client = require("react-dom/client");
|
|
2327
2415
|
var import_editor_elements5 = require("@elementor/editor-elements");
|
|
2328
2416
|
var import_editor_props4 = require("@elementor/editor-props");
|
|
2329
|
-
var
|
|
2417
|
+
var import_editor_v1_adapters12 = require("@elementor/editor-v1-adapters");
|
|
2330
2418
|
var import_i18n3 = require("@wordpress/i18n");
|
|
2331
2419
|
|
|
2332
2420
|
// src/legacy/replacements/base.ts
|
|
@@ -2607,7 +2695,7 @@ var InlineEditingReplacement = class extends ReplacementBase {
|
|
|
2607
2695
|
return !!this.inlineEditorRoot;
|
|
2608
2696
|
}
|
|
2609
2697
|
shouldRenderReplacement() {
|
|
2610
|
-
return this.isInlineEditingEligible() && (0,
|
|
2698
|
+
return this.isInlineEditingEligible() && (0, import_editor_v1_adapters12.getCurrentEditMode)() === "edit";
|
|
2611
2699
|
}
|
|
2612
2700
|
handleRenderInlineEditor = () => {
|
|
2613
2701
|
if (this.isEditingModeActive() || !this.isInlineEditingEligible()) {
|
|
@@ -2685,7 +2773,7 @@ var InlineEditingReplacement = class extends ReplacementBase {
|
|
|
2685
2773
|
content: parsed.content ? import_editor_props4.stringPropTypeUtil.create(parsed.content) : null,
|
|
2686
2774
|
children: parsed.children
|
|
2687
2775
|
});
|
|
2688
|
-
(0,
|
|
2776
|
+
(0, import_editor_v1_adapters12.undoable)(
|
|
2689
2777
|
{
|
|
2690
2778
|
do: () => {
|
|
2691
2779
|
const prevValue = this.getInlineEditablePropValue();
|
|
@@ -2727,7 +2815,7 @@ var InlineEditingReplacement = class extends ReplacementBase {
|
|
|
2727
2815
|
return null;
|
|
2728
2816
|
}
|
|
2729
2817
|
runCommand(key, value) {
|
|
2730
|
-
(0,
|
|
2818
|
+
(0, import_editor_v1_adapters12.__privateRunCommandSync)(
|
|
2731
2819
|
"document/elements/set-settings",
|
|
2732
2820
|
{
|
|
2733
2821
|
container: (0, import_editor_elements5.getContainer)(this.id),
|
|
@@ -2737,7 +2825,7 @@ var InlineEditingReplacement = class extends ReplacementBase {
|
|
|
2737
2825
|
},
|
|
2738
2826
|
{ internal: true }
|
|
2739
2827
|
);
|
|
2740
|
-
(0,
|
|
2828
|
+
(0, import_editor_v1_adapters12.__privateRunCommandSync)("document/save/set-is-modified", { status: true }, { internal: true });
|
|
2741
2829
|
}
|
|
2742
2830
|
getExpectedTag() {
|
|
2743
2831
|
const tagPropType = this.getTagPropType();
|
|
@@ -2889,7 +2977,7 @@ function registerElementType(type, elementTypeGenerator) {
|
|
|
2889
2977
|
elementsLegacyTypes[type] = elementTypeGenerator;
|
|
2890
2978
|
}
|
|
2891
2979
|
function initLegacyViews() {
|
|
2892
|
-
(0,
|
|
2980
|
+
(0, import_editor_v1_adapters13.__privateListenTo)((0, import_editor_v1_adapters13.v1ReadyEvent)(), () => {
|
|
2893
2981
|
const widgetsCache = (0, import_editor_elements6.getWidgetsCache)() ?? {};
|
|
2894
2982
|
const legacyWindow = window;
|
|
2895
2983
|
const renderer = createDomRenderer();
|
|
@@ -2968,7 +3056,7 @@ function initTabsModelExtensions() {
|
|
|
2968
3056
|
}
|
|
2969
3057
|
|
|
2970
3058
|
// src/mcp/resources/document-structure-resource.ts
|
|
2971
|
-
var
|
|
3059
|
+
var import_editor_v1_adapters14 = require("@elementor/editor-v1-adapters");
|
|
2972
3060
|
var DOCUMENT_STRUCTURE_URI = "elementor://document/structure";
|
|
2973
3061
|
var initDocumentStructureResource = (reg) => {
|
|
2974
3062
|
const { resource, sendResourceUpdated } = reg;
|
|
@@ -2981,14 +3069,14 @@ var initDocumentStructureResource = (reg) => {
|
|
|
2981
3069
|
sendResourceUpdated({ uri: DOCUMENT_STRUCTURE_URI });
|
|
2982
3070
|
}
|
|
2983
3071
|
};
|
|
2984
|
-
(0,
|
|
3072
|
+
(0, import_editor_v1_adapters14.__privateListenTo)(
|
|
2985
3073
|
[
|
|
2986
|
-
(0,
|
|
2987
|
-
(0,
|
|
2988
|
-
(0,
|
|
2989
|
-
(0,
|
|
2990
|
-
(0,
|
|
2991
|
-
(0,
|
|
3074
|
+
(0, import_editor_v1_adapters14.commandEndEvent)("document/elements/create"),
|
|
3075
|
+
(0, import_editor_v1_adapters14.commandEndEvent)("document/elements/delete"),
|
|
3076
|
+
(0, import_editor_v1_adapters14.commandEndEvent)("document/elements/move"),
|
|
3077
|
+
(0, import_editor_v1_adapters14.commandEndEvent)("document/elements/copy"),
|
|
3078
|
+
(0, import_editor_v1_adapters14.commandEndEvent)("document/elements/paste"),
|
|
3079
|
+
(0, import_editor_v1_adapters14.commandEndEvent)("editor/documents/attach-preview")
|
|
2992
3080
|
],
|
|
2993
3081
|
updateDocumentStructure
|
|
2994
3082
|
);
|
|
@@ -4197,14 +4285,14 @@ Note: The "size" property controls image resolution/loading, not visual size. Se
|
|
|
4197
4285
|
// src/prevent-link-in-link-commands.ts
|
|
4198
4286
|
var import_editor_elements12 = require("@elementor/editor-elements");
|
|
4199
4287
|
var import_editor_notifications3 = require("@elementor/editor-notifications");
|
|
4200
|
-
var
|
|
4288
|
+
var import_editor_v1_adapters15 = require("@elementor/editor-v1-adapters");
|
|
4201
4289
|
var import_i18n4 = require("@wordpress/i18n");
|
|
4202
4290
|
function initLinkInLinkPrevention() {
|
|
4203
|
-
(0,
|
|
4291
|
+
(0, import_editor_v1_adapters15.blockCommand)({
|
|
4204
4292
|
command: "document/elements/paste",
|
|
4205
4293
|
condition: blockLinkInLinkPaste
|
|
4206
4294
|
});
|
|
4207
|
-
(0,
|
|
4295
|
+
(0, import_editor_v1_adapters15.blockCommand)({
|
|
4208
4296
|
command: "document/elements/move",
|
|
4209
4297
|
condition: blockLinkInLinkMove
|
|
4210
4298
|
});
|
|
@@ -4280,7 +4368,7 @@ function shouldBlock(sourceElements, targetElements) {
|
|
|
4280
4368
|
// src/style-commands/paste-style.ts
|
|
4281
4369
|
var import_editor_elements15 = require("@elementor/editor-elements");
|
|
4282
4370
|
var import_editor_props10 = require("@elementor/editor-props");
|
|
4283
|
-
var
|
|
4371
|
+
var import_editor_v1_adapters17 = require("@elementor/editor-v1-adapters");
|
|
4284
4372
|
|
|
4285
4373
|
// src/utils/command-utils.ts
|
|
4286
4374
|
var import_editor_elements13 = require("@elementor/editor-elements");
|
|
@@ -4327,9 +4415,9 @@ function getTitleForContainers(containers) {
|
|
|
4327
4415
|
// src/style-commands/undoable-actions/paste-element-style.ts
|
|
4328
4416
|
var import_editor_elements14 = require("@elementor/editor-elements");
|
|
4329
4417
|
var import_editor_styles_repository4 = require("@elementor/editor-styles-repository");
|
|
4330
|
-
var
|
|
4418
|
+
var import_editor_v1_adapters16 = require("@elementor/editor-v1-adapters");
|
|
4331
4419
|
var import_i18n6 = require("@wordpress/i18n");
|
|
4332
|
-
var undoablePasteElementStyle = () => (0,
|
|
4420
|
+
var undoablePasteElementStyle = () => (0, import_editor_v1_adapters16.undoable)(
|
|
4333
4421
|
{
|
|
4334
4422
|
do: ({ containers, newStyle }) => {
|
|
4335
4423
|
return containers.map((container) => {
|
|
@@ -4405,12 +4493,12 @@ var undoablePasteElementStyle = () => (0, import_editor_v1_adapters15.undoable)(
|
|
|
4405
4493
|
// src/style-commands/paste-style.ts
|
|
4406
4494
|
function initPasteStyleCommand() {
|
|
4407
4495
|
const pasteElementStyleCommand = undoablePasteElementStyle();
|
|
4408
|
-
(0,
|
|
4496
|
+
(0, import_editor_v1_adapters17.blockCommand)({
|
|
4409
4497
|
command: "document/elements/paste-style",
|
|
4410
4498
|
condition: hasAtomicWidgets
|
|
4411
4499
|
});
|
|
4412
|
-
(0,
|
|
4413
|
-
(0,
|
|
4500
|
+
(0, import_editor_v1_adapters17.__privateListenTo)(
|
|
4501
|
+
(0, import_editor_v1_adapters17.commandStartEvent)("document/elements/paste-style"),
|
|
4414
4502
|
(e) => pasteStyles(e.args, pasteElementStyleCommand)
|
|
4415
4503
|
);
|
|
4416
4504
|
}
|
|
@@ -4461,14 +4549,14 @@ function pasteClasses(containers, classes) {
|
|
|
4461
4549
|
}
|
|
4462
4550
|
|
|
4463
4551
|
// src/style-commands/reset-style.ts
|
|
4464
|
-
var
|
|
4552
|
+
var import_editor_v1_adapters19 = require("@elementor/editor-v1-adapters");
|
|
4465
4553
|
|
|
4466
4554
|
// src/style-commands/undoable-actions/reset-element-style.ts
|
|
4467
4555
|
var import_editor_elements16 = require("@elementor/editor-elements");
|
|
4468
4556
|
var import_editor_styles_repository5 = require("@elementor/editor-styles-repository");
|
|
4469
|
-
var
|
|
4557
|
+
var import_editor_v1_adapters18 = require("@elementor/editor-v1-adapters");
|
|
4470
4558
|
var import_i18n7 = require("@wordpress/i18n");
|
|
4471
|
-
var undoableResetElementStyle = () => (0,
|
|
4559
|
+
var undoableResetElementStyle = () => (0, import_editor_v1_adapters18.undoable)(
|
|
4472
4560
|
{
|
|
4473
4561
|
do: ({ containers }) => {
|
|
4474
4562
|
return containers.map((container) => {
|
|
@@ -4512,12 +4600,12 @@ var undoableResetElementStyle = () => (0, import_editor_v1_adapters17.undoable)(
|
|
|
4512
4600
|
// src/style-commands/reset-style.ts
|
|
4513
4601
|
function initResetStyleCommand() {
|
|
4514
4602
|
const resetElementStyles = undoableResetElementStyle();
|
|
4515
|
-
(0,
|
|
4603
|
+
(0, import_editor_v1_adapters19.blockCommand)({
|
|
4516
4604
|
command: "document/elements/reset-style",
|
|
4517
4605
|
condition: hasAtomicWidgets
|
|
4518
4606
|
});
|
|
4519
|
-
(0,
|
|
4520
|
-
(0,
|
|
4607
|
+
(0, import_editor_v1_adapters19.__privateListenTo)(
|
|
4608
|
+
(0, import_editor_v1_adapters19.commandStartEvent)("document/elements/reset-style"),
|
|
4521
4609
|
(e) => resetStyles(e.args, resetElementStyles)
|
|
4522
4610
|
);
|
|
4523
4611
|
}
|
package/dist/index.mjs
CHANGED
|
@@ -543,7 +543,7 @@ import * as React4 from "react";
|
|
|
543
543
|
import {
|
|
544
544
|
__privateUseListenTo as useListenTo4,
|
|
545
545
|
commandEndEvent as commandEndEvent3,
|
|
546
|
-
getCanvasIframeDocument as
|
|
546
|
+
getCanvasIframeDocument as getCanvasIframeDocument4
|
|
547
547
|
} from "@elementor/editor-v1-adapters";
|
|
548
548
|
import { Portal as Portal2 } from "@elementor/ui";
|
|
549
549
|
|
|
@@ -617,6 +617,30 @@ function abortPreviousRuns(cb) {
|
|
|
617
617
|
};
|
|
618
618
|
}
|
|
619
619
|
|
|
620
|
+
// src/utils/pregenerated-links-removal.ts
|
|
621
|
+
import { getCanvasIframeDocument as getCanvasIframeDocument3 } from "@elementor/editor-v1-adapters";
|
|
622
|
+
var removedProviderKeys = /* @__PURE__ */ new Set();
|
|
623
|
+
function removeProviderPregeneratedLinks(providerKey, removePregeneratedLink) {
|
|
624
|
+
if (removedProviderKeys.has(providerKey)) {
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
const iframeDocument = getCanvasIframeDocument3();
|
|
628
|
+
if (!iframeDocument) {
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
const links = iframeDocument.head.querySelectorAll('link[rel="stylesheet"]');
|
|
632
|
+
links.forEach((link) => {
|
|
633
|
+
const { id, href, media } = link;
|
|
634
|
+
if (removePregeneratedLink({ id, href, media })) {
|
|
635
|
+
link.remove();
|
|
636
|
+
}
|
|
637
|
+
});
|
|
638
|
+
removedProviderKeys.add(providerKey);
|
|
639
|
+
}
|
|
640
|
+
function resetRemovedProviders() {
|
|
641
|
+
removedProviderKeys.clear();
|
|
642
|
+
}
|
|
643
|
+
|
|
620
644
|
// src/utils/signalized-process.ts
|
|
621
645
|
function signalizedProcess(signal, steps = []) {
|
|
622
646
|
return {
|
|
@@ -937,6 +961,7 @@ function useStyleItems() {
|
|
|
937
961
|
}, [providerAndSubscribers]);
|
|
938
962
|
useOnMount(() => {
|
|
939
963
|
registerDataHook2("after", "editor/documents/attach-preview", async () => {
|
|
964
|
+
resetRemovedProviders();
|
|
940
965
|
const promises = providerAndSubscribers.map(async ({ subscriber }) => subscriber());
|
|
941
966
|
await Promise.all(promises);
|
|
942
967
|
});
|
|
@@ -970,6 +995,9 @@ function createProviderSubscriber2({ provider, renderStyles, setStyleItems, cach
|
|
|
970
995
|
(abortController, previous, current) => signalizedProcess(abortController.signal).then((_, signal) => {
|
|
971
996
|
const hasDiffInfo = current !== void 0 && previous !== void 0;
|
|
972
997
|
const hasCache = cache.orderedIds.length > 0;
|
|
998
|
+
if (hasCache && provider.isPregeneratedLink) {
|
|
999
|
+
removeProviderPregeneratedLinks(provider.getKey(), provider.isPregeneratedLink);
|
|
1000
|
+
}
|
|
973
1001
|
if (hasDiffInfo && hasCache) {
|
|
974
1002
|
return updateItems(previous, current, signal);
|
|
975
1003
|
}
|
|
@@ -1083,7 +1111,7 @@ function StyleRenderer() {
|
|
|
1083
1111
|
return /* @__PURE__ */ React4.createElement(Portal2, { container }, filterUniqueStyleDefinitions(styleItems).map((item) => /* @__PURE__ */ React4.createElement("style", { key: `${item.id}-${item.breakpoint}-${item.state ?? "normal"}` }, item.value)), linksAttrs.map((attrs) => /* @__PURE__ */ React4.createElement("link", { ...attrs, key: attrs.id })));
|
|
1084
1112
|
}
|
|
1085
1113
|
function usePortalContainer2() {
|
|
1086
|
-
return useListenTo4(commandEndEvent3("editor/documents/attach-preview"), () =>
|
|
1114
|
+
return useListenTo4(commandEndEvent3("editor/documents/attach-preview"), () => getCanvasIframeDocument4()?.head);
|
|
1087
1115
|
}
|
|
1088
1116
|
function filterUniqueStyleDefinitions(styleItems) {
|
|
1089
1117
|
const seen = /* @__PURE__ */ new Map();
|
|
@@ -1391,14 +1419,74 @@ var plainTransformer = createTransformer((value) => {
|
|
|
1391
1419
|
return value;
|
|
1392
1420
|
});
|
|
1393
1421
|
|
|
1394
|
-
// src/transformers/shared/
|
|
1422
|
+
// src/transformers/shared/svg-src-transformer.ts
|
|
1423
|
+
import DOMPurify from "dompurify";
|
|
1395
1424
|
import { getMediaAttachment as getMediaAttachment2 } from "@elementor/wp-media";
|
|
1425
|
+
var SVG_INLINE_STYLES = "width: 100%; height: 100%; overflow: unset;";
|
|
1426
|
+
function processSvgContent(svgText) {
|
|
1427
|
+
const sanitized = DOMPurify.sanitize(svgText, {
|
|
1428
|
+
USE_PROFILES: { svg: true, svgFilters: true }
|
|
1429
|
+
});
|
|
1430
|
+
const parser = new DOMParser();
|
|
1431
|
+
const doc = parser.parseFromString(sanitized, "image/svg+xml");
|
|
1432
|
+
const svgElement = doc.querySelector("svg");
|
|
1433
|
+
if (!svgElement) {
|
|
1434
|
+
return null;
|
|
1435
|
+
}
|
|
1436
|
+
svgElement.setAttribute("fill", "currentColor");
|
|
1437
|
+
const existingStyle = svgElement.getAttribute("style") ?? "";
|
|
1438
|
+
const trimmed = existingStyle.trim();
|
|
1439
|
+
const merged = trimmed ? `${trimmed.replace(/;$/, "")}; ${SVG_INLINE_STYLES}` : SVG_INLINE_STYLES;
|
|
1440
|
+
svgElement.setAttribute("style", merged);
|
|
1441
|
+
return svgElement.outerHTML;
|
|
1442
|
+
}
|
|
1443
|
+
async function fetchSvgContent(url, signal) {
|
|
1444
|
+
try {
|
|
1445
|
+
const response = await fetch(url, { signal });
|
|
1446
|
+
if (!response.ok) {
|
|
1447
|
+
return null;
|
|
1448
|
+
}
|
|
1449
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
1450
|
+
const isSvg = contentType.includes("svg") || contentType.includes("xml") || url.endsWith(".svg");
|
|
1451
|
+
if (!isSvg) {
|
|
1452
|
+
return null;
|
|
1453
|
+
}
|
|
1454
|
+
return await response.text();
|
|
1455
|
+
} catch {
|
|
1456
|
+
return null;
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
function resolveSvgSrcId(id) {
|
|
1460
|
+
if (typeof id !== "number" || id <= 0) {
|
|
1461
|
+
return null;
|
|
1462
|
+
}
|
|
1463
|
+
return id;
|
|
1464
|
+
}
|
|
1465
|
+
var svgSrcTransformer = createTransformer(async (value, { signal }) => {
|
|
1466
|
+
const id = resolveSvgSrcId(value.id);
|
|
1467
|
+
const urlFromValue = typeof value.url === "string" ? value.url : null;
|
|
1468
|
+
let url = urlFromValue;
|
|
1469
|
+
if (id && !urlFromValue) {
|
|
1470
|
+
const attachment = await getMediaAttachment2({ id });
|
|
1471
|
+
url = attachment?.url ?? null;
|
|
1472
|
+
}
|
|
1473
|
+
const resolvedUrl = typeof url === "string" ? url : null;
|
|
1474
|
+
if (!resolvedUrl) {
|
|
1475
|
+
return { html: null, url: null };
|
|
1476
|
+
}
|
|
1477
|
+
const svgText = await fetchSvgContent(resolvedUrl, signal);
|
|
1478
|
+
const html = svgText ? processSvgContent(svgText) : null;
|
|
1479
|
+
return { html, url: resolvedUrl };
|
|
1480
|
+
});
|
|
1481
|
+
|
|
1482
|
+
// src/transformers/shared/video-src-transformer.ts
|
|
1483
|
+
import { getMediaAttachment as getMediaAttachment3 } from "@elementor/wp-media";
|
|
1396
1484
|
var videoSrcTransformer = createTransformer(async (value) => {
|
|
1397
1485
|
const { id, url } = value;
|
|
1398
1486
|
if (!id) {
|
|
1399
1487
|
return { id: null, url };
|
|
1400
1488
|
}
|
|
1401
|
-
const attachment = await
|
|
1489
|
+
const attachment = await getMediaAttachment3({ id });
|
|
1402
1490
|
return {
|
|
1403
1491
|
id,
|
|
1404
1492
|
url: attachment?.url ?? url
|
|
@@ -1407,7 +1495,7 @@ var videoSrcTransformer = createTransformer(async (value) => {
|
|
|
1407
1495
|
|
|
1408
1496
|
// src/init-settings-transformers.ts
|
|
1409
1497
|
function initSettingsTransformers() {
|
|
1410
|
-
settingsTransformersRegistry.register("classes", createClassesTransformer()).register("link", linkTransformer).register("query", queryTransformer).register("image", imageTransformer).register("image-src", imageSrcTransformer).register("video-src", videoSrcTransformer).register("attributes", attributesTransformer).register("date-time", dateTimeTransformer).register("html-v2", htmlV2Transformer).register("html-v3", htmlV3Transformer).registerFallback(plainTransformer);
|
|
1498
|
+
settingsTransformersRegistry.register("classes", createClassesTransformer()).register("link", linkTransformer).register("query", queryTransformer).register("image", imageTransformer).register("image-src", imageSrcTransformer).register("svg-src", svgSrcTransformer).register("video-src", videoSrcTransformer).register("attributes", attributesTransformer).register("date-time", dateTimeTransformer).register("html-v2", htmlV2Transformer).register("html-v3", htmlV3Transformer).registerFallback(plainTransformer);
|
|
1411
1499
|
}
|
|
1412
1500
|
|
|
1413
1501
|
// src/transformers/styles/background-color-overlay-transformer.ts
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elementor/editor-canvas",
|
|
3
3
|
"description": "Elementor Editor Canvas",
|
|
4
|
-
"version": "4.1.0-
|
|
4
|
+
"version": "4.1.0-738",
|
|
5
5
|
"private": false,
|
|
6
6
|
"author": "Elementor Team",
|
|
7
7
|
"homepage": "https://elementor.com/",
|
|
@@ -37,24 +37,25 @@
|
|
|
37
37
|
"react-dom": "^18.3.1"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@elementor/editor": "4.1.0-
|
|
41
|
-
"
|
|
42
|
-
"@elementor/editor-
|
|
43
|
-
"@elementor/editor-
|
|
44
|
-
"@elementor/editor-
|
|
45
|
-
"@elementor/editor-
|
|
46
|
-
"@elementor/editor-
|
|
47
|
-
"@elementor/editor-
|
|
48
|
-
"@elementor/editor-
|
|
49
|
-
"@elementor/editor-
|
|
50
|
-
"@elementor/editor-styles
|
|
51
|
-
"@elementor/editor-
|
|
52
|
-
"@elementor/editor-
|
|
53
|
-
"@elementor/
|
|
54
|
-
"@elementor/
|
|
40
|
+
"@elementor/editor": "4.1.0-738",
|
|
41
|
+
"dompurify": "^3.2.6",
|
|
42
|
+
"@elementor/editor-controls": "4.1.0-738",
|
|
43
|
+
"@elementor/editor-documents": "4.1.0-738",
|
|
44
|
+
"@elementor/editor-elements": "4.1.0-738",
|
|
45
|
+
"@elementor/editor-interactions": "4.1.0-738",
|
|
46
|
+
"@elementor/editor-mcp": "4.1.0-738",
|
|
47
|
+
"@elementor/editor-notifications": "4.1.0-738",
|
|
48
|
+
"@elementor/editor-props": "4.1.0-738",
|
|
49
|
+
"@elementor/editor-responsive": "4.1.0-738",
|
|
50
|
+
"@elementor/editor-styles": "4.1.0-738",
|
|
51
|
+
"@elementor/editor-styles-repository": "4.1.0-738",
|
|
52
|
+
"@elementor/editor-ui": "4.1.0-738",
|
|
53
|
+
"@elementor/editor-v1-adapters": "4.1.0-738",
|
|
54
|
+
"@elementor/schema": "4.1.0-738",
|
|
55
|
+
"@elementor/twing": "4.1.0-738",
|
|
55
56
|
"@elementor/ui": "1.36.17",
|
|
56
|
-
"@elementor/utils": "4.1.0-
|
|
57
|
-
"@elementor/wp-media": "4.1.0-
|
|
57
|
+
"@elementor/utils": "4.1.0-738",
|
|
58
|
+
"@elementor/wp-media": "4.1.0-738",
|
|
58
59
|
"@floating-ui/react": "^0.27.5",
|
|
59
60
|
"@wordpress/i18n": "^5.13.0"
|
|
60
61
|
},
|
|
@@ -6,6 +6,7 @@ import { registerDataHook } from '@elementor/editor-v1-adapters';
|
|
|
6
6
|
|
|
7
7
|
import { type RendererStyleDefinition, type StyleItem, type StyleRenderer } from '../renderers/create-styles-renderer';
|
|
8
8
|
import { abortPreviousRuns } from '../utils/abort-previous-runs';
|
|
9
|
+
import { removeProviderPregeneratedLinks, resetRemovedProviders } from '../utils/pregenerated-links-removal';
|
|
9
10
|
import { signalizedProcess } from '../utils/signalized-process';
|
|
10
11
|
import { useOnMount } from './use-on-mount';
|
|
11
12
|
import { useStylePropResolver } from './use-style-prop-resolver';
|
|
@@ -69,6 +70,8 @@ export function useStyleItems() {
|
|
|
69
70
|
|
|
70
71
|
useOnMount( () => {
|
|
71
72
|
registerDataHook( 'after', 'editor/documents/attach-preview', async () => {
|
|
73
|
+
resetRemovedProviders();
|
|
74
|
+
|
|
72
75
|
const promises = providerAndSubscribers.map( async ( { subscriber } ) => subscriber() );
|
|
73
76
|
|
|
74
77
|
await Promise.all( promises );
|
|
@@ -136,6 +139,11 @@ function createProviderSubscriber( { provider, renderStyles, setStyleItems, cach
|
|
|
136
139
|
const hasDiffInfo = current !== undefined && previous !== undefined;
|
|
137
140
|
const hasCache = cache.orderedIds.length > 0;
|
|
138
141
|
|
|
142
|
+
if ( hasCache && provider.isPregeneratedLink ) {
|
|
143
|
+
// if styles were rendered already (i.e. hasCache = true), we can safely remove the pregenerated css rules imported via <link /> tags
|
|
144
|
+
removeProviderPregeneratedLinks( provider.getKey(), provider.isPregeneratedLink );
|
|
145
|
+
}
|
|
146
|
+
|
|
139
147
|
if ( hasDiffInfo && hasCache ) {
|
|
140
148
|
return updateItems( previous, current, signal );
|
|
141
149
|
}
|
|
@@ -9,6 +9,7 @@ import { queryTransformer } from './transformers/settings/query-transformer';
|
|
|
9
9
|
import { imageSrcTransformer } from './transformers/shared/image-src-transformer';
|
|
10
10
|
import { imageTransformer } from './transformers/shared/image-transformer';
|
|
11
11
|
import { plainTransformer } from './transformers/shared/plain-transformer';
|
|
12
|
+
import { svgSrcTransformer } from './transformers/shared/svg-src-transformer';
|
|
12
13
|
import { videoSrcTransformer } from './transformers/shared/video-src-transformer';
|
|
13
14
|
|
|
14
15
|
export function initSettingsTransformers() {
|
|
@@ -18,6 +19,7 @@ export function initSettingsTransformers() {
|
|
|
18
19
|
.register( 'query', queryTransformer )
|
|
19
20
|
.register( 'image', imageTransformer )
|
|
20
21
|
.register( 'image-src', imageSrcTransformer )
|
|
22
|
+
.register( 'svg-src', svgSrcTransformer )
|
|
21
23
|
.register( 'video-src', videoSrcTransformer )
|
|
22
24
|
.register( 'attributes', attributesTransformer )
|
|
23
25
|
.register( 'date-time', dateTimeTransformer )
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { getMediaAttachment } from '@elementor/wp-media';
|
|
2
|
+
|
|
3
|
+
import { svgSrcTransformer } from '../svg-src-transformer';
|
|
4
|
+
|
|
5
|
+
jest.mock( '@elementor/wp-media' );
|
|
6
|
+
|
|
7
|
+
const mockedGetMediaAttachment = jest.mocked( getMediaAttachment );
|
|
8
|
+
|
|
9
|
+
const SAMPLE_SVG = '<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100"><path d="M0 0h100v100H0z"/></svg>';
|
|
10
|
+
|
|
11
|
+
const mockFetch = ( body: string, contentType = 'image/svg+xml', ok = true ) => {
|
|
12
|
+
global.fetch = jest.fn().mockResolvedValue( {
|
|
13
|
+
ok,
|
|
14
|
+
headers: new Headers( { 'content-type': contentType } ),
|
|
15
|
+
text: () => Promise.resolve( body ),
|
|
16
|
+
} );
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
describe( 'svgSrcTransformer', () => {
|
|
20
|
+
beforeEach( () => {
|
|
21
|
+
jest.clearAllMocks();
|
|
22
|
+
} );
|
|
23
|
+
|
|
24
|
+
afterEach( () => {
|
|
25
|
+
jest.restoreAllMocks();
|
|
26
|
+
} );
|
|
27
|
+
|
|
28
|
+
it( 'fetches SVG and returns processed html with currentColor fill', async () => {
|
|
29
|
+
// Arrange.
|
|
30
|
+
mockFetch( SAMPLE_SVG );
|
|
31
|
+
|
|
32
|
+
// Act.
|
|
33
|
+
const result = await svgSrcTransformer( { id: null, url: 'https://example.com/icon.svg' }, { key: 'svg' } );
|
|
34
|
+
|
|
35
|
+
// Assert.
|
|
36
|
+
expect( result ).toEqual( {
|
|
37
|
+
html: expect.stringContaining( 'fill="currentColor"' ),
|
|
38
|
+
url: 'https://example.com/icon.svg',
|
|
39
|
+
} );
|
|
40
|
+
} );
|
|
41
|
+
|
|
42
|
+
it( 'adds inline styles to the svg element', async () => {
|
|
43
|
+
// Arrange.
|
|
44
|
+
mockFetch( SAMPLE_SVG );
|
|
45
|
+
|
|
46
|
+
// Act.
|
|
47
|
+
const result = ( await svgSrcTransformer(
|
|
48
|
+
{ id: null, url: 'https://example.com/icon.svg' },
|
|
49
|
+
{ key: 'svg' }
|
|
50
|
+
) ) as { html: string; url: string };
|
|
51
|
+
|
|
52
|
+
// Assert.
|
|
53
|
+
expect( result.html ).toContain( 'width: 100%' );
|
|
54
|
+
expect( result.html ).toContain( 'height: 100%' );
|
|
55
|
+
expect( result.html ).toContain( 'overflow: unset' );
|
|
56
|
+
} );
|
|
57
|
+
|
|
58
|
+
it( 'returns null html when url is null', async () => {
|
|
59
|
+
// Arrange & Act.
|
|
60
|
+
const result = await svgSrcTransformer( { id: null, url: null }, { key: 'svg' } );
|
|
61
|
+
|
|
62
|
+
// Assert.
|
|
63
|
+
expect( result ).toEqual( { html: null, url: null } );
|
|
64
|
+
expect( mockedGetMediaAttachment ).not.toHaveBeenCalled();
|
|
65
|
+
} );
|
|
66
|
+
|
|
67
|
+
it( 'resolves url from attachment id then fetches SVG', async () => {
|
|
68
|
+
// Arrange.
|
|
69
|
+
mockedGetMediaAttachment.mockResolvedValue( {
|
|
70
|
+
url: 'https://example.com/resolved.svg',
|
|
71
|
+
} as never );
|
|
72
|
+
mockFetch( SAMPLE_SVG );
|
|
73
|
+
|
|
74
|
+
// Act.
|
|
75
|
+
const result = await svgSrcTransformer( { id: 42, url: null }, { key: 'svg' } );
|
|
76
|
+
|
|
77
|
+
// Assert.
|
|
78
|
+
expect( mockedGetMediaAttachment ).toHaveBeenCalledWith( { id: 42 } );
|
|
79
|
+
expect( result ).toEqual( {
|
|
80
|
+
html: expect.stringContaining( 'fill="currentColor"' ),
|
|
81
|
+
url: 'https://example.com/resolved.svg',
|
|
82
|
+
} );
|
|
83
|
+
} );
|
|
84
|
+
|
|
85
|
+
it( 'falls back to provided url when attachment lookup fails', async () => {
|
|
86
|
+
// Arrange.
|
|
87
|
+
mockedGetMediaAttachment.mockResolvedValue( null as never );
|
|
88
|
+
mockFetch( SAMPLE_SVG );
|
|
89
|
+
|
|
90
|
+
// Act.
|
|
91
|
+
const result = ( await svgSrcTransformer(
|
|
92
|
+
{ id: 99, url: 'https://example.com/fallback.svg' },
|
|
93
|
+
{ key: 'svg' }
|
|
94
|
+
) ) as { html: string; url: string };
|
|
95
|
+
|
|
96
|
+
// Assert.
|
|
97
|
+
expect( result.url ).toBe( 'https://example.com/fallback.svg' );
|
|
98
|
+
expect( result.html ).toContain( 'fill="currentColor"' );
|
|
99
|
+
} );
|
|
100
|
+
|
|
101
|
+
it( 'returns null html when id is set but attachment and url are missing', async () => {
|
|
102
|
+
// Arrange.
|
|
103
|
+
mockedGetMediaAttachment.mockResolvedValue( null as never );
|
|
104
|
+
|
|
105
|
+
// Act.
|
|
106
|
+
const result = await svgSrcTransformer( { id: 7, url: null }, { key: 'svg' } );
|
|
107
|
+
|
|
108
|
+
// Assert.
|
|
109
|
+
expect( result ).toEqual( { html: null, url: null } );
|
|
110
|
+
} );
|
|
111
|
+
|
|
112
|
+
it( 'returns null html when fetch fails', async () => {
|
|
113
|
+
// Arrange.
|
|
114
|
+
mockFetch( '', 'image/svg+xml', false );
|
|
115
|
+
|
|
116
|
+
// Act.
|
|
117
|
+
const result = await svgSrcTransformer( { id: null, url: 'https://example.com/missing.svg' }, { key: 'svg' } );
|
|
118
|
+
|
|
119
|
+
// Assert.
|
|
120
|
+
expect( result ).toEqual( {
|
|
121
|
+
html: null,
|
|
122
|
+
url: 'https://example.com/missing.svg',
|
|
123
|
+
} );
|
|
124
|
+
} );
|
|
125
|
+
|
|
126
|
+
it( 'returns null html when content is not SVG', async () => {
|
|
127
|
+
// Arrange.
|
|
128
|
+
mockFetch( '<html><body>Not SVG</body></html>', 'text/html' );
|
|
129
|
+
|
|
130
|
+
// Act.
|
|
131
|
+
const result = await svgSrcTransformer( { id: null, url: 'https://example.com/page.html' }, { key: 'svg' } );
|
|
132
|
+
|
|
133
|
+
// Assert.
|
|
134
|
+
expect( result ).toEqual( {
|
|
135
|
+
html: null,
|
|
136
|
+
url: 'https://example.com/page.html',
|
|
137
|
+
} );
|
|
138
|
+
} );
|
|
139
|
+
|
|
140
|
+
it( 'merges with existing style attribute', async () => {
|
|
141
|
+
// Arrange.
|
|
142
|
+
const svgWithStyle = '<svg xmlns="http://www.w3.org/2000/svg" style="display: block"><path d="M0 0"/></svg>';
|
|
143
|
+
mockFetch( svgWithStyle );
|
|
144
|
+
|
|
145
|
+
// Act.
|
|
146
|
+
const result = ( await svgSrcTransformer(
|
|
147
|
+
{ id: null, url: 'https://example.com/styled.svg' },
|
|
148
|
+
{ key: 'svg' }
|
|
149
|
+
) ) as { html: string; url: string };
|
|
150
|
+
|
|
151
|
+
// Assert.
|
|
152
|
+
expect( result.html ).toContain( 'display: block' );
|
|
153
|
+
expect( result.html ).toContain( 'width: 100%' );
|
|
154
|
+
} );
|
|
155
|
+
|
|
156
|
+
it( 'returns null html when response has no svg element', async () => {
|
|
157
|
+
// Arrange.
|
|
158
|
+
mockFetch( '<div>Not an SVG</div>' );
|
|
159
|
+
|
|
160
|
+
// Act.
|
|
161
|
+
const result = await svgSrcTransformer( { id: null, url: 'https://example.com/not-svg.svg' }, { key: 'svg' } );
|
|
162
|
+
|
|
163
|
+
// Assert.
|
|
164
|
+
expect( result ).toEqual( {
|
|
165
|
+
html: null,
|
|
166
|
+
url: 'https://example.com/not-svg.svg',
|
|
167
|
+
} );
|
|
168
|
+
} );
|
|
169
|
+
|
|
170
|
+
it( 'passes abort signal to fetch', async () => {
|
|
171
|
+
// Arrange.
|
|
172
|
+
mockFetch( SAMPLE_SVG );
|
|
173
|
+
const controller = new AbortController();
|
|
174
|
+
|
|
175
|
+
// Act.
|
|
176
|
+
await svgSrcTransformer(
|
|
177
|
+
{ id: null, url: 'https://example.com/icon.svg' },
|
|
178
|
+
{ key: 'svg', signal: controller.signal }
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
// Assert.
|
|
182
|
+
expect( global.fetch ).toHaveBeenCalledWith( 'https://example.com/icon.svg', { signal: controller.signal } );
|
|
183
|
+
} );
|
|
184
|
+
} );
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import DOMPurify from 'dompurify';
|
|
2
|
+
import { getMediaAttachment } from '@elementor/wp-media';
|
|
3
|
+
|
|
4
|
+
import { createTransformer } from '../create-transformer';
|
|
5
|
+
import type { TransformerOptions } from '../types';
|
|
6
|
+
|
|
7
|
+
type SvgSrc = {
|
|
8
|
+
id?: unknown;
|
|
9
|
+
url?: unknown;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const SVG_INLINE_STYLES = 'width: 100%; height: 100%; overflow: unset;';
|
|
13
|
+
|
|
14
|
+
function processSvgContent( svgText: string ): string | null {
|
|
15
|
+
const sanitized = DOMPurify.sanitize( svgText, {
|
|
16
|
+
USE_PROFILES: { svg: true, svgFilters: true },
|
|
17
|
+
} );
|
|
18
|
+
|
|
19
|
+
const parser = new DOMParser();
|
|
20
|
+
const doc = parser.parseFromString( sanitized, 'image/svg+xml' );
|
|
21
|
+
const svgElement = doc.querySelector( 'svg' );
|
|
22
|
+
|
|
23
|
+
if ( ! svgElement ) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
svgElement.setAttribute( 'fill', 'currentColor' );
|
|
28
|
+
|
|
29
|
+
const existingStyle = svgElement.getAttribute( 'style' ) ?? '';
|
|
30
|
+
const trimmed = existingStyle.trim();
|
|
31
|
+
const merged = trimmed ? `${ trimmed.replace( /;$/, '' ) }; ${ SVG_INLINE_STYLES }` : SVG_INLINE_STYLES;
|
|
32
|
+
svgElement.setAttribute( 'style', merged );
|
|
33
|
+
|
|
34
|
+
return svgElement.outerHTML;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function fetchSvgContent( url: string, signal?: AbortSignal ): Promise< string | null > {
|
|
38
|
+
try {
|
|
39
|
+
const response = await fetch( url, { signal } );
|
|
40
|
+
|
|
41
|
+
if ( ! response.ok ) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const contentType = response.headers.get( 'content-type' ) ?? '';
|
|
46
|
+
const isSvg = contentType.includes( 'svg' ) || contentType.includes( 'xml' ) || url.endsWith( '.svg' );
|
|
47
|
+
|
|
48
|
+
if ( ! isSvg ) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return await response.text();
|
|
53
|
+
} catch {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function resolveSvgSrcId( id: unknown ): number | null {
|
|
59
|
+
if ( typeof id !== 'number' || id <= 0 ) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return id;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export const svgSrcTransformer = createTransformer( async ( value: SvgSrc, { signal }: TransformerOptions ) => {
|
|
67
|
+
const id = resolveSvgSrcId( value.id );
|
|
68
|
+
const urlFromValue = typeof value.url === 'string' ? value.url : null;
|
|
69
|
+
|
|
70
|
+
let url: string | null | undefined = urlFromValue;
|
|
71
|
+
|
|
72
|
+
if ( id && ! urlFromValue ) {
|
|
73
|
+
const attachment = await getMediaAttachment( { id } );
|
|
74
|
+
url = attachment?.url ?? null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const resolvedUrl = typeof url === 'string' ? url : null;
|
|
78
|
+
|
|
79
|
+
if ( ! resolvedUrl ) {
|
|
80
|
+
return { html: null, url: null };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const svgText = await fetchSvgContent( resolvedUrl, signal );
|
|
84
|
+
const html = svgText ? processSvgContent( svgText ) : null;
|
|
85
|
+
|
|
86
|
+
return { html, url: resolvedUrl };
|
|
87
|
+
} );
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { type PregeneratedLinkItem } from '@elementor/editor-styles-repository';
|
|
2
|
+
import { getCanvasIframeDocument } from '@elementor/editor-v1-adapters';
|
|
3
|
+
|
|
4
|
+
import { removeProviderPregeneratedLinks, resetRemovedProviders } from '../pregenerated-links-removal';
|
|
5
|
+
|
|
6
|
+
jest.mock( '@elementor/editor-v1-adapters', () => ( {
|
|
7
|
+
getCanvasIframeDocument: jest.fn(),
|
|
8
|
+
} ) );
|
|
9
|
+
|
|
10
|
+
function createLink( id: string, href: string, media: string = 'all' ): HTMLLinkElement {
|
|
11
|
+
const link = document.createElement( 'link' );
|
|
12
|
+
link.setAttribute( 'rel', 'stylesheet' );
|
|
13
|
+
link.setAttribute( 'id', id );
|
|
14
|
+
link.setAttribute( 'href', href );
|
|
15
|
+
link.setAttribute( 'media', media );
|
|
16
|
+
|
|
17
|
+
return link;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
describe( 'pregenerated-links-removal', () => {
|
|
21
|
+
const createHead = () => {
|
|
22
|
+
const head = document.createElement( 'head' );
|
|
23
|
+
head.appendChild( createLink( 'base-desktop-css', 'base-desktop.css' ) );
|
|
24
|
+
head.appendChild( createLink( 'base-tablet-css', 'base-tablet.css' ) );
|
|
25
|
+
head.appendChild( createLink( 'global-preview-desktop-css', 'global-preview-desktop.css' ) );
|
|
26
|
+
head.appendChild( createLink( 'global-preview-mobile_extra-css', 'global-preview-mobile_extra.css' ) );
|
|
27
|
+
head.appendChild( createLink( 'local-123-preview-desktop-css', 'local-123-preview-desktop.css' ) );
|
|
28
|
+
head.appendChild( createLink( 'local-456-preview-desktop-css', 'local-456-preview-desktop.css' ) );
|
|
29
|
+
head.appendChild( createLink( 'elementor-post-123-css', 'elementor-post-123.css' ) );
|
|
30
|
+
head.appendChild( createLink( 'other-plugin-css', 'other-plugin.css' ) );
|
|
31
|
+
return head;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
beforeEach( () => {
|
|
35
|
+
resetRemovedProviders();
|
|
36
|
+
} );
|
|
37
|
+
|
|
38
|
+
describe( 'removeProviderPregeneratedLinks', () => {
|
|
39
|
+
it( 'should remove links matching global pattern', () => {
|
|
40
|
+
// Arrange.
|
|
41
|
+
const head = createHead();
|
|
42
|
+
const mockDocument = { head } as Document;
|
|
43
|
+
jest.mocked( getCanvasIframeDocument ).mockReturnValue( mockDocument );
|
|
44
|
+
|
|
45
|
+
// Act.
|
|
46
|
+
removeProviderPregeneratedLinks( 'global-classes', ( { id } ) =>
|
|
47
|
+
/^global-(preview|frontend)-[a-zA-Z_-]+-css$/.test( id )
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
// Assert.
|
|
51
|
+
const remainingLinkIds = Array.from( head.querySelectorAll( 'link' ) ).map( ( link ) =>
|
|
52
|
+
link.getAttribute( 'id' )
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
expect( remainingLinkIds ).not.toContain( 'global-preview-desktop-css' );
|
|
56
|
+
expect( remainingLinkIds ).not.toContain( 'global-preview-mobile_extra-css' );
|
|
57
|
+
expect( remainingLinkIds ).toContain( 'base-desktop-css' );
|
|
58
|
+
expect( remainingLinkIds ).toContain( 'local-123-preview-desktop-css' );
|
|
59
|
+
expect( remainingLinkIds ).toContain( 'other-plugin-css' );
|
|
60
|
+
} );
|
|
61
|
+
|
|
62
|
+
it( 'should remove links matching local pattern', () => {
|
|
63
|
+
// Arrange.
|
|
64
|
+
const head = createHead();
|
|
65
|
+
const mockDocument = { head } as Document;
|
|
66
|
+
jest.mocked( getCanvasIframeDocument ).mockReturnValue( mockDocument );
|
|
67
|
+
|
|
68
|
+
// Act.
|
|
69
|
+
removeProviderPregeneratedLinks( 'document-elements-123', ( { id } ) =>
|
|
70
|
+
/^local-\d+-(preview|frontend)-[a-zA-Z_-]+-css$/.test( id )
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
// Assert.
|
|
74
|
+
const remainingLinkIds = Array.from( head.querySelectorAll( 'link' ) ).map( ( link ) =>
|
|
75
|
+
link.getAttribute( 'id' )
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
expect( remainingLinkIds ).not.toContain( 'local-123-preview-desktop-css' );
|
|
79
|
+
expect( remainingLinkIds ).not.toContain( 'local-456-preview-desktop-css' );
|
|
80
|
+
expect( remainingLinkIds ).toContain( 'global-preview-desktop-css' );
|
|
81
|
+
expect( remainingLinkIds ).toContain( 'base-desktop-css' );
|
|
82
|
+
} );
|
|
83
|
+
|
|
84
|
+
it( 'should not remove links when provider was already processed', () => {
|
|
85
|
+
// Arrange.
|
|
86
|
+
const head = createHead();
|
|
87
|
+
const mockDocument = { head } as Document;
|
|
88
|
+
jest.mocked( getCanvasIframeDocument ).mockReturnValue( mockDocument );
|
|
89
|
+
|
|
90
|
+
const initialLinkCount = head.querySelectorAll( 'link' ).length;
|
|
91
|
+
|
|
92
|
+
// Act.
|
|
93
|
+
removeProviderPregeneratedLinks( 'global-classes', ( { id } ) =>
|
|
94
|
+
/^global-(preview|frontend)-[a-zA-Z_-]+-css$/.test( id )
|
|
95
|
+
);
|
|
96
|
+
const afterFirstRemoval = head.querySelectorAll( 'link' ).length;
|
|
97
|
+
|
|
98
|
+
removeProviderPregeneratedLinks( 'global-classes', ( { id } ) =>
|
|
99
|
+
/^global-(preview|frontend)-[a-zA-Z_-]+-css$/.test( id )
|
|
100
|
+
);
|
|
101
|
+
const afterSecondCall = head.querySelectorAll( 'link' ).length;
|
|
102
|
+
|
|
103
|
+
// Assert.
|
|
104
|
+
expect( afterFirstRemoval ).toBeLessThan( initialLinkCount );
|
|
105
|
+
expect( afterSecondCall ).toBe( afterFirstRemoval );
|
|
106
|
+
} );
|
|
107
|
+
|
|
108
|
+
it( 'should handle different providers independently', () => {
|
|
109
|
+
// Arrange.
|
|
110
|
+
const head = createHead();
|
|
111
|
+
const mockDocument = { head } as Document;
|
|
112
|
+
jest.mocked( getCanvasIframeDocument ).mockReturnValue( mockDocument );
|
|
113
|
+
|
|
114
|
+
// Act.
|
|
115
|
+
removeProviderPregeneratedLinks( 'global-classes', ( { id } ) =>
|
|
116
|
+
/^global-(preview|frontend)-[a-zA-Z_-]+-css$/.test( id )
|
|
117
|
+
);
|
|
118
|
+
removeProviderPregeneratedLinks( 'document-elements-123', ( { id } ) =>
|
|
119
|
+
/^local-\d+-(preview|frontend)-[a-zA-Z_-]+-css$/.test( id )
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
// Assert.
|
|
123
|
+
const remainingLinkIds = Array.from( head.querySelectorAll( 'link' ) ).map( ( link ) =>
|
|
124
|
+
link.getAttribute( 'id' )
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
expect( remainingLinkIds ).not.toContain( 'global-preview-desktop-css' );
|
|
128
|
+
expect( remainingLinkIds ).not.toContain( 'local-123-preview-desktop-css' );
|
|
129
|
+
expect( remainingLinkIds ).toContain( 'base-desktop-css' );
|
|
130
|
+
expect( remainingLinkIds ).toContain( 'elementor-post-123-css' );
|
|
131
|
+
expect( remainingLinkIds ).toContain( 'other-plugin-css' );
|
|
132
|
+
} );
|
|
133
|
+
|
|
134
|
+
it( 'should do nothing when iframe document is not available', () => {
|
|
135
|
+
// Arrange.
|
|
136
|
+
jest.mocked( getCanvasIframeDocument ).mockReturnValue( null );
|
|
137
|
+
|
|
138
|
+
// Act & Assert - should not throw.
|
|
139
|
+
expect( () => {
|
|
140
|
+
removeProviderPregeneratedLinks( 'global-classes', ( { id } ) =>
|
|
141
|
+
/^global-(preview|frontend)-[a-zA-Z_-]+-css$/.test( id )
|
|
142
|
+
);
|
|
143
|
+
} ).not.toThrow();
|
|
144
|
+
} );
|
|
145
|
+
|
|
146
|
+
it( 'should pass id, path, and media from each link to the predicate', () => {
|
|
147
|
+
const head = document.createElement( 'head' );
|
|
148
|
+
|
|
149
|
+
const link1Params = {
|
|
150
|
+
id: 'test-123',
|
|
151
|
+
href: 'test-123.css',
|
|
152
|
+
media: 'all',
|
|
153
|
+
};
|
|
154
|
+
const link2Params = {
|
|
155
|
+
id: 'test-456',
|
|
156
|
+
href: 'alternative-test-456.css',
|
|
157
|
+
media: 'screen and (max-width: 1024px)',
|
|
158
|
+
};
|
|
159
|
+
const link1 = createLink( link1Params.id, link1Params.href, link1Params.media );
|
|
160
|
+
const link2 = createLink( link2Params.id, link2Params.href, link2Params.media );
|
|
161
|
+
head.appendChild( link1 );
|
|
162
|
+
head.appendChild( link2 );
|
|
163
|
+
|
|
164
|
+
const mockDocument = { head } as Document;
|
|
165
|
+
jest.mocked( getCanvasIframeDocument ).mockReturnValue( mockDocument );
|
|
166
|
+
|
|
167
|
+
const received: PregeneratedLinkItem[] = [];
|
|
168
|
+
removeProviderPregeneratedLinks( 'pregenerated-link-attrs', ( item ) => {
|
|
169
|
+
received.push( item );
|
|
170
|
+
return false;
|
|
171
|
+
} );
|
|
172
|
+
|
|
173
|
+
expect( received ).toMatchObject( [
|
|
174
|
+
{ ...link1Params, href: link1.href },
|
|
175
|
+
{ ...link2Params, href: link2.href },
|
|
176
|
+
] );
|
|
177
|
+
} );
|
|
178
|
+
} );
|
|
179
|
+
|
|
180
|
+
describe( 'resetRemovedProviders', () => {
|
|
181
|
+
it( 'should allow re-removal after reset', () => {
|
|
182
|
+
// Arrange.
|
|
183
|
+
const head = createHead();
|
|
184
|
+
const mockDocument = { head } as Document;
|
|
185
|
+
jest.mocked( getCanvasIframeDocument ).mockReturnValue( mockDocument );
|
|
186
|
+
|
|
187
|
+
removeProviderPregeneratedLinks( 'global-classes', ( { id } ) =>
|
|
188
|
+
/^global-(preview|frontend)-[a-zA-Z_-]+-css$/.test( id )
|
|
189
|
+
);
|
|
190
|
+
const countAfterFirstRemoval = head.querySelectorAll( 'link' ).length;
|
|
191
|
+
|
|
192
|
+
head.appendChild( createLink( 'global-preview-widescreen-css', 'global-preview-widescreen.css' ) );
|
|
193
|
+
|
|
194
|
+
const countAfterAddingNewLink = head.querySelectorAll( 'link' ).length;
|
|
195
|
+
|
|
196
|
+
// Act.
|
|
197
|
+
resetRemovedProviders();
|
|
198
|
+
removeProviderPregeneratedLinks( 'global-classes', ( { id } ) =>
|
|
199
|
+
/^global-(preview|frontend)-[a-zA-Z_-]+-css$/.test( id )
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
// Assert.
|
|
203
|
+
const countAfterSecondRemoval = head.querySelectorAll( 'link' ).length;
|
|
204
|
+
expect( countAfterAddingNewLink ).toBe( countAfterFirstRemoval + 1 );
|
|
205
|
+
expect( countAfterSecondRemoval ).toBe( countAfterFirstRemoval );
|
|
206
|
+
} );
|
|
207
|
+
} );
|
|
208
|
+
} );
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type PregeneratedLinkItem } from '@elementor/editor-styles-repository';
|
|
2
|
+
import { getCanvasIframeDocument } from '@elementor/editor-v1-adapters';
|
|
3
|
+
|
|
4
|
+
const removedProviderKeys = new Set< string >();
|
|
5
|
+
|
|
6
|
+
export function removeProviderPregeneratedLinks(
|
|
7
|
+
providerKey: string,
|
|
8
|
+
removePregeneratedLink: ( pregeneratedLinkItem: PregeneratedLinkItem ) => boolean
|
|
9
|
+
): void {
|
|
10
|
+
if ( removedProviderKeys.has( providerKey ) ) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const iframeDocument = getCanvasIframeDocument();
|
|
15
|
+
|
|
16
|
+
if ( ! iframeDocument ) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const links = iframeDocument.head.querySelectorAll< HTMLLinkElement >( 'link[rel="stylesheet"]' );
|
|
21
|
+
|
|
22
|
+
links.forEach( ( link ) => {
|
|
23
|
+
const { id, href, media } = link;
|
|
24
|
+
|
|
25
|
+
if ( removePregeneratedLink( { id, href, media } ) ) {
|
|
26
|
+
link.remove();
|
|
27
|
+
}
|
|
28
|
+
} );
|
|
29
|
+
|
|
30
|
+
removedProviderKeys.add( providerKey );
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function resetRemovedProviders(): void {
|
|
34
|
+
removedProviderKeys.clear();
|
|
35
|
+
}
|