PluginProbe
Extendify / 3.0.4
Extendify v3.0.4
3.2.1 3.2.0 3.1.6 3.1.5 3.1.4 3.1.3 3.1.2 3.1.1 3.1.0 3.0.6 3.0.5 3.0.4 trunk 0.1.0 0.10.0 0.10.1 0.10.2 0.11.0 0.11.1 0.2.0 0.3.0 0.3.1 0.4.0 0.5.0 0.6.0 All 127 releases
extendify / src / Agent / lib / svg-helpers.js

svg-helpers.js in Extendify 3.0.4, at src/Agent/lib/svg-helpers.js

163 lines 4.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import { colord } from 'colord';
2
3 const getDoc = () =>
4 document.querySelector('iframe[name="editor-canvas"]')?.contentDocument ||
5 document;
6
7 const SVGFeFunc = ['feFuncR', 'feFuncG', 'feFuncB'];
8
9 export const replaceDuotoneSVG = (() => {
10 const originalStates = new Map();
11
12 const processSingleDuotone = (slug, duotoneTheme, dynamicDuotone) => {
13 let dynamicSlug = '';
14 if (!Number.isNaN(Number(slug))) {
15 dynamicSlug = slug;
16 slug = dynamicDuotone?.[`wp-duotone-${dynamicSlug}`];
17 }
18
19 // Extract duotone colors from theme settings for the specified slug
20 const colors = duotoneTheme?.find((item) => item.slug === slug)?.colors;
21
22 // Duotone requires exactly 2 colors (dark and light)
23 if (!colors || colors.length !== 2) return false;
24
25 const doc = getDoc();
26 const filterId = `wp-duotone-${dynamicSlug || slug}`;
27
28 const element =
29 doc.querySelector(`svg#${filterId}`) ||
30 doc.querySelector(`filter#${filterId}`);
31
32 if (!element) return false;
33
34 // Capture the original state only once per slug
35 if (!originalStates.has(dynamicSlug || slug)) {
36 const originalState = {
37 tableValues: {},
38 cssProperty: getComputedStyle(doc.documentElement).getPropertyValue(
39 `--wp--preset--duotone--${dynamicSlug || slug}`,
40 ),
41 };
42
43 // Store original tableValues for each filter function element
44 SVGFeFunc.forEach((func) => {
45 const funcElement = element.querySelector(func);
46 if (funcElement) {
47 originalState.tableValues[func] =
48 funcElement.getAttribute('tableValues');
49 }
50 });
51
52 originalStates.set(dynamicSlug || slug, originalState);
53 }
54
55 // Convert hex colors to normalized RGB values (0-1 range for SVG filters)
56 const [darkColor, lightColor] = colors.map((hex) => {
57 const { r, g, b } = colord(hex).toRgb();
58 return { r: r / 255, g: g / 255, b: b / 255 };
59 });
60
61 // Update SVG filter function elements for each color channel
62 // WordPress duotone filters use feFuncR/G/B elements with tableValues
63 // that map original color values to new duotone colors
64 SVGFeFunc.forEach((func, index) => {
65 const component = ['r', 'g', 'b'][index];
66 const funcElement = element.querySelector(func);
67 if (funcElement) {
68 // Set tableValues to map from dark to light color for this channel
69 // Format: "darkValue lightValue" (SVG filter table lookup)
70 funcElement.setAttribute(
71 'tableValues',
72 `${darkColor[component]} ${lightColor[component]}`,
73 );
74 }
75 });
76
77 // Update the CSS custom property so WordPress can reference the updated filter
78 doc.documentElement.style.setProperty(
79 `--wp--preset--duotone--${dynamicSlug || slug}`,
80 `url(#${filterId})`,
81 );
82
83 return true;
84 };
85
86 return ({ duotoneTheme, dynamicDuotone }) => {
87 const processedSlugs = [];
88 const doc = getDoc();
89
90 const duotoneElements = doc.querySelectorAll('[id^="wp-duotone-"]');
91
92 duotoneElements.forEach((element) => {
93 const detectedSlug = element.id.replace('wp-duotone-', '');
94 if (processSingleDuotone(detectedSlug, duotoneTheme, dynamicDuotone)) {
95 processedSlugs.push(detectedSlug);
96 }
97 });
98
99 // Inject CSS filter rules for duotone classes
100 if (processedSlugs.length > 0) {
101 let style = doc.getElementById('extendify-duotone-overrides');
102 if (!style) {
103 style = doc.createElement('style');
104 style.id = 'extendify-duotone-overrides';
105 doc.head.appendChild(style);
106 }
107 style.textContent = processedSlugs
108 .map(
109 (slug) =>
110 `.wp-duotone-${slug} img { filter: url(#wp-duotone-${slug}) !important; }`,
111 )
112 .join('\n');
113 }
114
115 // Return the cleanup function directly
116 return processedSlugs.length > 0
117 ? () => {
118 const cleanupDoc = getDoc();
119
120 processedSlugs.forEach((processedSlug) => {
121 const originalState = originalStates.get(processedSlug);
122 if (!originalState) return;
123
124 const filterId = `wp-duotone-${processedSlug}`;
125 const currentElement =
126 cleanupDoc.querySelector(`svg#${filterId}`) ||
127 cleanupDoc.querySelector(`filter#${filterId}`);
128 if (!currentElement) return;
129
130 // Restore original tableValues for each filter function element
131 SVGFeFunc.forEach((func) => {
132 const funcElement = currentElement.querySelector(func);
133 if (funcElement && originalState.tableValues[func] !== null) {
134 if (originalState.tableValues[func]) {
135 funcElement.setAttribute(
136 'tableValues',
137 originalState.tableValues[func],
138 );
139 } else {
140 funcElement.removeAttribute('tableValues');
141 }
142 }
143 });
144
145 // Restore original CSS custom property
146 if (originalState.cssProperty) {
147 cleanupDoc.documentElement.style.setProperty(
148 `--wp--preset--duotone--${processedSlug}`,
149 originalState.cssProperty,
150 );
151 } else {
152 cleanupDoc.documentElement.style.removeProperty(
153 `--wp--preset--duotone--${processedSlug}`,
154 );
155 }
156 });
157
158 cleanupDoc.getElementById('extendify-duotone-overrides')?.remove();
159 }
160 : null;
161 };
162 })();
163