PluginProbe
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor / 2.0.7
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor v2.0.7
2.0.13 2.0.12 2.0.11 2.0.10 2.0.9 trunk 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.0.8
blockenberg / blocks / color-thief / frontend.js

frontend.js in Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor 2.0.7, at blocks/color-thief/frontend.js

328 lines 15.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 (function () {
2 'use strict';
3
4 var _typoKeys = {
5 family:'font-family', weight:'font-weight', style:'font-style',
6 transform:'text-transform', decoration:'text-decoration',
7 sizeDesktop:'font-size-d', sizeTablet:'font-size-t', sizeMobile:'font-size-m',
8 lineHeightDesktop:'line-height-d', lineHeightTablet:'line-height-t', lineHeightMobile:'line-height-m',
9 letterSpacingDesktop:'letter-spacing-d', letterSpacingTablet:'letter-spacing-t', letterSpacingMobile:'letter-spacing-m',
10 wordSpacingDesktop:'word-spacing-d', wordSpacingTablet:'word-spacing-t', wordSpacingMobile:'word-spacing-m'
11 };
12 var _typoUnits = { size:'sizeUnit', lineHeight:'lineHeightUnit', letterSpacing:'letterSpacingUnit', wordSpacing:'wordSpacingUnit' };
13 var _typoUnitDefaults = { size:'px', lineHeight:'', letterSpacing:'px', wordSpacing:'px' };
14 function typoCssVarsForEl(el, obj, prefix) {
15 if (!obj || typeof obj !== 'object') return;
16 Object.keys(_typoKeys).forEach(function (k) {
17 var v = obj[k]; if (v === undefined || v === '') return;
18 var prop = _typoKeys[k];
19 var base = k.replace(/Desktop|Tablet|Mobile/, '');
20 var uKey = _typoUnits[base];
21 if (uKey && typeof v === 'number') v = v + (obj[uKey] || _typoUnitDefaults[base] || '');
22 el.style.setProperty(prefix + prop, v);
23 });
24 }
25
26 // ── Colour utilities ─────────────────────────────────────────────
27 function rgbToHex(r, g, b) {
28 return '#' + [r, g, b].map(function (v) {
29 return ('0' + Math.round(v).toString(16)).slice(-2);
30 }).join('');
31 }
32
33 function rgbToHsl(r, g, b) {
34 r /= 255; g /= 255; b /= 255;
35 var max = Math.max(r, g, b), min = Math.min(r, g, b);
36 var h, s, l = (max + min) / 2;
37 if (max === min) { h = s = 0; }
38 else {
39 var d = max - min;
40 s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
41 switch (max) {
42 case r: h = ((g - b) / d + (g < b ? 6 : 0)) / 6; break;
43 case g: h = ((b - r) / d + 2) / 6; break;
44 default: h = ((r - g) / d + 4) / 6; break;
45 }
46 }
47 return [Math.round(h * 360), Math.round(s * 100), Math.round(l * 100)];
48 }
49
50 // ── K-means palette extraction ───────────────────────────────────
51 function extractPalette(img, numColors, quality) {
52 var canvas = document.createElement('canvas');
53 var scale = Math.min(1, 150 / Math.max(img.naturalWidth, img.naturalHeight, 1));
54 canvas.width = Math.max(1, Math.floor(img.naturalWidth * scale));
55 canvas.height = Math.max(1, Math.floor(img.naturalHeight * scale));
56 var ctx = canvas.getContext('2d');
57 ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
58 var data;
59 try { data = ctx.getImageData(0, 0, canvas.width, canvas.height).data; }
60 catch (e) { return null; } // CORS
61
62 var pixels = [];
63 for (var i = 0; i < data.length; i += 4 * (quality || 10)) {
64 if (data[i + 3] < 128) continue;
65 pixels.push([data[i], data[i + 1], data[i + 2]]);
66 }
67 if (pixels.length < numColors) return null;
68
69 // K-means
70 var k = Math.min(numColors, pixels.length);
71 var centroids = [];
72 for (var ci = 0; ci < k; ci++) {
73 var idx = Math.floor((ci / k) * pixels.length);
74 centroids.push(pixels[idx].slice());
75 }
76
77 for (var iter = 0; iter < 8; iter++) {
78 var clusters = centroids.map(function () { return []; });
79 pixels.forEach(function (p) {
80 var best = 0, bestD = Infinity;
81 centroids.forEach(function (c, j) {
82 var d = (p[0]-c[0])*(p[0]-c[0]) + (p[1]-c[1])*(p[1]-c[1]) + (p[2]-c[2])*(p[2]-c[2]);
83 if (d < bestD) { bestD = d; best = j; }
84 });
85 clusters[best].push(p);
86 });
87 var changed = false;
88 clusters.forEach(function (cl, j) {
89 if (!cl.length) return;
90 var nr = cl.reduce(function (s, p) { return s + p[0]; }, 0) / cl.length;
91 var ng = cl.reduce(function (s, p) { return s + p[1]; }, 0) / cl.length;
92 var nb = cl.reduce(function (s, p) { return s + p[2]; }, 0) / cl.length;
93 if (Math.abs(nr - centroids[j][0]) + Math.abs(ng - centroids[j][1]) + Math.abs(nb - centroids[j][2]) > 1) changed = true;
94 centroids[j] = [nr, ng, nb];
95 });
96 if (!changed) break;
97 }
98
99 return centroids.map(function (c) {
100 return { r: Math.round(c[0]), g: Math.round(c[1]), b: Math.round(c[2]) };
101 });
102 }
103
104 // ── Copy to clipboard helper ─────────────────────────────────────
105 function copyText(txt) {
106 if (navigator.clipboard && navigator.clipboard.writeText) {
107 navigator.clipboard.writeText(txt).catch(function () {});
108 } else {
109 var ta = document.createElement('textarea');
110 ta.value = txt; ta.style.position = 'fixed'; ta.style.opacity = '0';
111 document.body.appendChild(ta); ta.select();
112 try { document.execCommand('copy'); } catch (e) {}
113 document.body.removeChild(ta);
114 }
115 }
116
117 // ── Init one block ───────────────────────────────────────────────
118 function initColorThief(appEl) {
119 var raw = appEl.dataset.opts;
120 if (!raw) return;
121 var opts;
122 try { opts = JSON.parse(raw); } catch (e) { return; }
123
124 // Set typography CSS vars on the parent wrapper
125 var parentWrap = appEl.closest('.wp-block-blockenberg-color-thief') || appEl.parentElement;
126 if (parentWrap) {
127 typoCssVarsForEl(parentWrap, opts.typoLabel, '--bkbg-cth-lb-');
128 }
129
130 var size = opts.swatchSize || 60;
131 var radius = opts.swatchRadius || 8;
132 var gap = opts.swatchGap || 8;
133 var numColors = opts.paletteSize || 6;
134 var quality = opts.quality || 10;
135 var layout = opts.layout || 'row';
136 var copyFmt = opts.copyFormat || 'hex';
137 var padding = opts.padding || 20;
138 var domRadius = opts.borderRadius || 12;
139
140 // Build containing block
141 var block = document.createElement('div');
142 block.style.backgroundColor = opts.bgColor || '#ffffff';
143 block.style.borderRadius = domRadius + 'px';
144 block.style.border = '1px solid ' + (opts.borderColor || '#e5e7eb');
145 block.style.padding = padding + 'px';
146 block.style.overflow = 'hidden';
147
148 // Image preview
149 var imgEl = null;
150 if (opts.showImagePreview && opts.imageUrl) {
151 var imgWrap = document.createElement('div');
152 imgWrap.className = 'bkbg-ct-image-wrap';
153 imgWrap.style.borderRadius = Math.max(0, domRadius - 4) + 'px';
154 imgEl = document.createElement('img');
155 imgEl.src = opts.imageUrl;
156 imgEl.alt = opts.imageAlt || '';
157 imgEl.crossOrigin = 'anonymous';
158 imgEl.style.height = (opts.imageHeight || 200) + 'px';
159 imgEl.style.objectFit = opts.imageObjectFit || 'cover';
160 imgWrap.appendChild(imgEl);
161 block.appendChild(imgWrap);
162 }
163
164 var placeholderEl = null;
165 if (!opts.imageUrl) {
166 placeholderEl = document.createElement('div');
167 placeholderEl.style.cssText = 'padding:24px;text-align:center;color:#9ca3af;';
168 placeholderEl.textContent = 'No image selected.';
169 block.appendChild(placeholderEl);
170 }
171
172 appEl.parentNode.replaceChild(block, appEl);
173 if (!opts.imageUrl) return;
174
175 // Process after image loads
176 function process(img) {
177 var palette = extractPalette(img, numColors, quality);
178 if (!palette || !palette.length) {
179 var errEl = document.createElement('div');
180 errEl.style.cssText = 'color:#ef4444;font-size:13px;padding:8px;';
181 errEl.textContent = '⚠ Could not extract palette. Image may have CORS restrictions.';
182 block.appendChild(errEl);
183 return;
184 }
185
186 // Dominant colour banner
187 if (opts.showDominant) {
188 var dom = palette[0];
189 var domEl = document.createElement('div');
190 domEl.className = 'bkbg-ct-dominant';
191 domEl.style.background = rgbToHex(dom.r, dom.g, dom.b);
192 domEl.textContent = 'DOMINANT: ' + rgbToHex(dom.r, dom.g, dom.b).toUpperCase();
193 block.appendChild(domEl);
194 }
195
196 // Swatch container
197 var swatchesEl = document.createElement('div');
198 swatchesEl.className = 'bkbg-ct-swatches' + (layout === 'list' ? ' bkbg-ct-list' : '');
199 if (layout === 'grid') {
200 swatchesEl.style.display = 'grid';
201 swatchesEl.style.gridTemplateColumns = 'repeat(' + (opts.swatchColumns || 6) + ', 1fr)';
202 swatchesEl.style.gap = gap + 'px';
203 } else if (layout === 'list') {
204 swatchesEl.style.display = 'flex';
205 swatchesEl.style.flexDirection = 'column';
206 swatchesEl.style.gap = gap + 'px';
207 } else {
208 swatchesEl.style.display = 'flex';
209 swatchesEl.style.flexWrap = 'wrap';
210 swatchesEl.style.gap = gap + 'px';
211 swatchesEl.style.justifyContent = 'center';
212 }
213
214 palette.forEach(function (c) {
215 var hex = rgbToHex(c.r, c.g, c.b);
216 var rgb = [c.r, c.g, c.b];
217 var hsl = rgbToHsl(c.r, c.g, c.b);
218 var hexLabel = hex.toUpperCase();
219 var rgbLabel = 'rgb(' + rgb.join(', ') + ')';
220 var hslLabel = 'hsl(' + hsl[0] + ', ' + hsl[1] + '%, ' + hsl[2] + '%)';
221 var copyValue = copyFmt === 'rgb' ? rgbLabel : copyFmt === 'hsl' ? hslLabel : hexLabel;
222 var displayLabel = opts.showHexValues ? hexLabel : opts.showRgbValues ? rgbLabel : opts.showHslValues ? hslLabel : '';
223
224 if (layout === 'list') {
225 var item = document.createElement('div');
226 item.className = 'bkbg-ct-list-item';
227
228 var chip = document.createElement('div');
229 chip.className = 'bkbg-ct-swatch-color';
230 chip.style.width = '40px'; chip.style.height = '40px';
231 chip.style.borderRadius = radius + 'px';
232 chip.style.background = hex;
233 chip.style.flexShrink = '0';
234 item.appendChild(chip);
235
236 var info = document.createElement('div');
237 info.className = 'bkbg-ct-list-info';
238 info.style.color = opts.labelColor || '#374151';
239 if (opts.showHexValues) { var hl = document.createElement('span'); hl.style.fontFamily = 'monospace'; hl.textContent = hexLabel; info.appendChild(hl); }
240 if (opts.showRgbValues) { var rl = document.createElement('span'); rl.style.fontFamily = 'monospace'; rl.textContent = rgbLabel; info.appendChild(rl); }
241 if (opts.showHslValues) { var sl = document.createElement('span'); sl.style.fontFamily = 'monospace'; sl.textContent = hslLabel; info.appendChild(sl); }
242 item.appendChild(info);
243
244 if (opts.showCopyButton) {
245 var btn = document.createElement('button');
246 btn.className = 'bkbg-ct-copy-btn';
247 btn.style.color = opts.labelColor || '#374151';
248 btn.textContent = 'Copy';
249 btn.addEventListener('click', function () {
250 copyText(copyValue);
251 btn.textContent = 'Copied!';
252 setTimeout(function () { btn.textContent = 'Copy'; }, 1500);
253 });
254 item.appendChild(btn);
255 }
256
257 swatchesEl.appendChild(item);
258 } else {
259 var swatch = document.createElement('div');
260 swatch.className = 'bkbg-ct-swatch';
261
262 var colorBox = document.createElement('div');
263 colorBox.className = 'bkbg-ct-swatch-color' + (opts.showCopyButton ? ' bkbg-ct-copyable' : '');
264 colorBox.style.width = size + 'px';
265 colorBox.style.height = size + 'px';
266 colorBox.style.borderRadius = radius + 'px';
267 colorBox.style.background = hex;
268 colorBox.title = copyValue;
269
270 if (opts.showCopyButton) {
271 colorBox.addEventListener('click', function () {
272 copyText(copyValue);
273 colorBox.classList.add('bkbg-ct-copied');
274 setTimeout(function () { colorBox.classList.remove('bkbg-ct-copied'); }, 1200);
275 });
276 }
277
278 swatch.appendChild(colorBox);
279
280 if (displayLabel) {
281 var labelEl = document.createElement('span');
282 labelEl.className = 'bkbg-ct-swatch-label';
283 labelEl.style.color = opts.labelColor || '#374151';
284 labelEl.style.maxWidth = size + 'px';
285 labelEl.textContent = displayLabel;
286 swatch.appendChild(labelEl);
287 }
288
289 swatchesEl.appendChild(swatch);
290 }
291 });
292
293 block.appendChild(swatchesEl);
294 }
295
296 // Load image
297 if (imgEl) {
298 if (imgEl.complete && imgEl.naturalWidth > 0) {
299 process(imgEl);
300 } else {
301 imgEl.addEventListener('load', function () { process(imgEl); });
302 imgEl.addEventListener('error', function () {
303 // Try without crossOrigin for non-CORS servers
304 var img2 = new Image();
305 img2.onload = function () { process(img2); };
306 img2.onerror = function () {};
307 img2.src = opts.imageUrl;
308 });
309 }
310 } else if (opts.imageUrl && !opts.showImagePreview) {
311 // Load hidden canvas image for palette extraction only
312 var hiddenImg = new Image();
313 hiddenImg.crossOrigin = 'anonymous';
314 hiddenImg.onload = function () { process(hiddenImg); };
315 hiddenImg.onerror = function () {};
316 hiddenImg.src = opts.imageUrl;
317 }
318 }
319
320 document.addEventListener('DOMContentLoaded', function () {
321 document.querySelectorAll('.bkbg-ct-app').forEach(initColorThief);
322 });
323
324 if (document.readyState === 'complete' || document.readyState === 'interactive') {
325 document.querySelectorAll('.bkbg-ct-app').forEach(initColorThief);
326 }
327 })();
328