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 / image-placeholder / frontend.js

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

388 lines 15.4 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 typoMap = [
5 ['family','font-family'],['weight','font-weight'],['style','font-style'],
6 ['decoration','text-decoration'],['transform','text-transform'],
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 function typoCssVarsForEl(typo, prefix, el) {
13 if (!typo || typeof typo !== 'object') return;
14 for (var i = 0; i < typoMap.length; i++) {
15 var v = typo[typoMap[i][0]];
16 if (v !== undefined && v !== '' && v !== null) el.style.setProperty(prefix + typoMap[i][1], String(v));
17 }
18 }
19
20 var PRESETS = [
21 { label: '1:1', w: 400, h: 400 },
22 { label: '4:3', w: 800, h: 600 },
23 { label: '16:9', w: 1280, h: 720 },
24 { label: '3:2', w: 900, h: 600 },
25 { label: '2:3', w: 400, h: 600 },
26 { label: '9:16', w: 360, h: 640 },
27 { label: 'HD 1080p', w: 1920, h: 1080 },
28 { label: 'Social Sq.', w: 1080, h: 1080 }
29 ];
30
31 function drawPlaceholder(canvas, opts) {
32 var w = opts.width, h = opts.height, bg = opts.bg, fg = opts.fg, text = opts.text, theme = opts.theme, accent = opts.accent;
33 canvas.width = w;
34 canvas.height = h;
35 var ctx = canvas.getContext('2d');
36
37 /* Background */
38 if (theme === 'gradient') {
39 var grad = ctx.createLinearGradient(0, 0, w, h);
40 grad.addColorStop(0, bg);
41 grad.addColorStop(1, accent || '#999');
42 ctx.fillStyle = grad;
43 } else if (theme === 'pattern') {
44 ctx.fillStyle = bg;
45 ctx.fillRect(0, 0, w, h);
46 ctx.fillStyle = accent ? accent + '33' : 'rgba(0,0,0,0.07)';
47 var step = Math.round(Math.max(w, h) / 20);
48 for (var y = -h; y < h * 2; y += step) {
49 for (var x = -w; x < w * 2; x += step) {
50 ctx.fillRect(x, y, step / 2, step / 2);
51 }
52 }
53 } else {
54 ctx.fillStyle = bg;
55 }
56 ctx.fillRect(0, 0, w, h);
57
58 /* Diagonal cross lines (like classic placeholder) */
59 ctx.strokeStyle = fg + '22';
60 ctx.lineWidth = Math.max(1, Math.round(Math.min(w, h) / 100));
61 ctx.beginPath();
62 ctx.moveTo(0, 0); ctx.lineTo(w, h);
63 ctx.moveTo(w, 0); ctx.lineTo(0, h);
64 ctx.stroke();
65
66 /* Border */
67 ctx.strokeStyle = fg + '44';
68 ctx.lineWidth = Math.max(2, Math.round(Math.min(w, h) / 80));
69 ctx.strokeRect(ctx.lineWidth, ctx.lineWidth, w - ctx.lineWidth * 2, h - ctx.lineWidth * 2);
70
71 /* Text */
72 var displayText = text || (w + ' × ' + h);
73 var fontSize = Math.max(12, Math.min(Math.round(Math.min(w, h) / 8), 72));
74 ctx.fillStyle = fg;
75 ctx.textAlign = 'center';
76 ctx.textBaseline = 'middle';
77 ctx.font = 'bold ' + fontSize + 'px system-ui,Arial,sans-serif';
78
79 /* Word wrap */
80 var maxWidth = w * 0.8;
81 var words = displayText.split(' ');
82 var lines = [];
83 var currentLine = '';
84 words.forEach(function (word) {
85 var testLine = currentLine ? currentLine + ' ' + word : word;
86 if (ctx.measureText(testLine).width > maxWidth && currentLine) {
87 lines.push(currentLine);
88 currentLine = word;
89 } else {
90 currentLine = testLine;
91 }
92 });
93 if (currentLine) lines.push(currentLine);
94
95 var lineH = fontSize * 1.3;
96 var totalTextH = lines.length * lineH;
97 var startY = (h - totalTextH) / 2 + lineH / 2;
98 lines.forEach(function (line, i) {
99 ctx.fillText(line, w / 2, startY + i * lineH);
100 });
101
102 /* Dimensions sub-label */
103 if (text) {
104 var subSize = Math.max(10, Math.round(fontSize * 0.5));
105 ctx.font = subSize + 'px system-ui,Arial,sans-serif';
106 ctx.fillStyle = fg + 'aa';
107 ctx.fillText(w + ' × ' + h, w / 2, startY + lines.length * lineH + subSize);
108 }
109 }
110
111 function initBlock(root) {
112 var opts;
113 try { opts = JSON.parse(root.getAttribute('data-opts')); } catch (e) { return; }
114 var a = opts;
115
116 var state = {
117 width: a.defaultWidth || 800,
118 height: a.defaultHeight || 600,
119 bg: a.defaultBgColor || '#cccccc',
120 fg: a.defaultTextColor || '#555555',
121 text: a.defaultText || '',
122 theme: a.defaultTheme || 'flat'
123 };
124
125 root.innerHTML = '';
126
127 var wrap = document.createElement('div');
128 wrap.className = 'bkbg-iph-wrap';
129 wrap.style.cssText = 'background:' + a.sectionBg + ';max-width:' + a.contentMaxWidth + 'px;margin:0 auto;';
130 typoCssVarsForEl(a.titleTypo, '--bkbg-iph-tt-', wrap);
131 root.appendChild(wrap);
132
133 if (a.showTitle) {
134 var h = document.createElement('div');
135 h.className = 'bkbg-iph-title';
136 h.style.color = a.titleColor;
137 h.textContent = a.title;
138 wrap.appendChild(h);
139 }
140
141 /* Controls */
142 var controls = document.createElement('div');
143 controls.className = 'bkbg-iph-controls';
144 controls.style.cssText = 'background:' + a.cardBg + ';border-color:' + a.borderColor + ';';
145 wrap.appendChild(controls);
146
147 function makeField(lbl, el) {
148 var g = document.createElement('div');
149 g.className = 'bkbg-iph-field';
150 var l = document.createElement('label');
151 l.className = 'bkbg-iph-label';
152 l.style.color = a.labelColor;
153 l.textContent = lbl;
154 g.appendChild(l);
155 g.appendChild(el);
156 return g;
157 }
158
159 var wInput = document.createElement('input');
160 wInput.className = 'bkbg-iph-input';
161 wInput.type = 'number';
162 wInput.min = '1'; wInput.max = '4000';
163 wInput.value = state.width;
164 controls.appendChild(makeField('Width (px)', wInput));
165
166 var hInput = document.createElement('input');
167 hInput.className = 'bkbg-iph-input';
168 hInput.type = 'number';
169 hInput.min = '1'; hInput.max = '4000';
170 hInput.value = state.height;
171 controls.appendChild(makeField('Height (px)', hInput));
172
173 var textInput = document.createElement('input');
174 textInput.className = 'bkbg-iph-input';
175 textInput.style.minWidth = '150px';
176 textInput.placeholder = 'Custom text (default: dimensions)';
177 textInput.value = state.text;
178 controls.appendChild(makeField('Custom Text', textInput));
179
180 var bgField = document.createElement('div');
181 bgField.className = 'bkbg-iph-field bkbg-iph-color-field';
182 var bgLbl = document.createElement('label');
183 bgLbl.className = 'bkbg-iph-label';
184 bgLbl.style.color = a.labelColor;
185 bgLbl.textContent = 'BG';
186 var bgInput = document.createElement('input');
187 bgInput.className = 'bkbg-iph-color-input';
188 bgInput.type = 'color';
189 bgInput.value = state.bg;
190 bgField.appendChild(bgLbl);
191 bgField.appendChild(bgInput);
192 controls.appendChild(bgField);
193
194 var fgField = document.createElement('div');
195 fgField.className = 'bkbg-iph-field bkbg-iph-color-field';
196 var fgLbl = document.createElement('label');
197 fgLbl.className = 'bkbg-iph-label';
198 fgLbl.style.color = a.labelColor;
199 fgLbl.textContent = 'Text';
200 var fgInput = document.createElement('input');
201 fgInput.className = 'bkbg-iph-color-input';
202 fgInput.type = 'color';
203 fgInput.value = state.fg;
204 fgField.appendChild(fgLbl);
205 fgField.appendChild(fgInput);
206 controls.appendChild(fgField);
207
208 /* Presets */
209 if (a.showAspectPresets) {
210 var presetsEl = document.createElement('div');
211 presetsEl.className = 'bkbg-iph-presets';
212 wrap.appendChild(presetsEl);
213
214 PRESETS.forEach(function (p) {
215 var btn = document.createElement('button');
216 btn.className = 'bkbg-iph-preset-btn';
217 btn.style.cssText = 'border-color:' + a.accentColor + ';color:' + a.accentColor + ';background:transparent;';
218 btn.textContent = p.label + ' (' + p.w + '×' + p.h + ')';
219 btn.addEventListener('click', function () {
220 state.width = p.w;
221 state.height = p.h;
222 wInput.value = p.w;
223 hInput.value = p.h;
224 render();
225 });
226 presetsEl.appendChild(btn);
227 });
228 }
229
230 /* Theme selector */
231 if (a.showThemeSelector) {
232 var themeRow = document.createElement('div');
233 themeRow.className = 'bkbg-iph-theme-row';
234 wrap.appendChild(themeRow);
235
236 ['flat','gradient','pattern'].forEach(function (t) {
237 var btn = document.createElement('button');
238 btn.className = 'bkbg-iph-theme-btn';
239 btn.textContent = t.charAt(0).toUpperCase() + t.slice(1);
240 btn.dataset.theme = t;
241 applyThemeBtnStyle(btn, t === state.theme);
242 btn.addEventListener('click', function () {
243 state.theme = t;
244 themeRow.querySelectorAll('.bkbg-iph-theme-btn').forEach(function (b) {
245 applyThemeBtnStyle(b, b.dataset.theme === t);
246 });
247 render();
248 });
249 themeRow.appendChild(btn);
250 });
251 }
252
253 function applyThemeBtnStyle(btn, active) {
254 btn.style.cssText = 'border-color:' + a.accentColor + ';background:' + (active ? a.accentColor : 'transparent') + ';color:' + (active ? '#fff' : a.accentColor) + ';';
255 }
256
257 /* Canvas preview */
258 var previewWrap = document.createElement('div');
259 previewWrap.className = 'bkbg-iph-preview-wrap';
260 wrap.appendChild(previewWrap);
261
262 var canvasWrap = document.createElement('div');
263 canvasWrap.className = 'bkbg-iph-canvas-wrap';
264 previewWrap.appendChild(canvasWrap);
265
266 var canvas = document.createElement('canvas');
267 canvasWrap.appendChild(canvas);
268
269 /* Dims label */
270 var dimsEl = document.createElement('div');
271 dimsEl.className = 'bkbg-iph-dims';
272 dimsEl.style.color = a.subtitleColor;
273 wrap.appendChild(dimsEl);
274
275 /* Actions */
276 var actionsEl = document.createElement('div');
277 actionsEl.className = 'bkbg-iph-actions';
278 wrap.appendChild(actionsEl);
279
280 var urlBox = document.createElement('div');
281 urlBox.className = 'bkbg-iph-url-box';
282 var urlLbl = document.createElement('div');
283 urlLbl.className = 'bkbg-iph-url-label';
284 urlLbl.textContent = 'Data URI';
285 urlBox.appendChild(urlLbl);
286 var urlText = document.createElement('div');
287 urlText.className = 'bkbg-iph-url-text';
288 urlBox.appendChild(urlText);
289 wrap.appendChild(urlBox);
290
291 if (a.showDownloadBtn) {
292 var dlBtn = document.createElement('button');
293 dlBtn.className = 'bkbg-iph-btn';
294 dlBtn.style.background = a.accentColor;
295 dlBtn.textContent = '⬇ Download PNG';
296 dlBtn.addEventListener('click', function () {
297 var link = document.createElement('a');
298 link.download = 'placeholder-' + state.width + 'x' + state.height + '.png';
299 link.href = canvas.toDataURL('image/png');
300 link.click();
301 });
302 actionsEl.appendChild(dlBtn);
303 }
304
305 if (a.showCopyUriBtn) {
306 var copyUriBtn = document.createElement('button');
307 copyUriBtn.className = 'bkbg-iph-btn bkbg-iph-btn-outline';
308 copyUriBtn.style.cssText = 'border-color:' + a.accentColor + ';color:' + a.accentColor + ';';
309 copyUriBtn.textContent = 'Copy Data URI';
310 copyUriBtn.addEventListener('click', function () {
311 var uri = canvas.toDataURL('image/png');
312 if (navigator.clipboard) {
313 navigator.clipboard.writeText(uri).then(function () {
314 copyUriBtn.textContent = '✓ Copied!';
315 setTimeout(function () { copyUriBtn.textContent = 'Copy Data URI'; }, 1800);
316 });
317 }
318 urlText.textContent = uri.slice(0, 200) + '';
319 urlBox.classList.add('bkbg-iph-show');
320 });
321 actionsEl.appendChild(copyUriBtn);
322 }
323
324 if (a.showCopyUrlBtn) {
325 var copyUrlBtn = document.createElement('button');
326 copyUrlBtn.className = 'bkbg-iph-btn bkbg-iph-btn-outline';
327 copyUrlBtn.style.cssText = 'border-color:#6b7280;color:#6b7280;';
328 copyUrlBtn.textContent = 'Copy URL';
329 copyUrlBtn.title = 'Copies a placeholder.com style URL';
330 copyUrlBtn.addEventListener('click', function () {
331 var url = 'https://via.placeholder.com/' + state.width + 'x' + state.height;
332 if (navigator.clipboard) {
333 navigator.clipboard.writeText(url).then(function () {
334 copyUrlBtn.textContent = '✓ Copied URL!';
335 setTimeout(function () { copyUrlBtn.textContent = 'Copy URL'; }, 1800);
336 });
337 }
338 });
339 actionsEl.appendChild(copyUrlBtn);
340 }
341
342 function render() {
343 var maxPreviewW = Math.min(state.width, wrap.offsetWidth - 48 || 600);
344 var scale = maxPreviewW / state.width;
345 var previewH = Math.round(state.height * scale);
346
347 drawPlaceholder(canvas, {
348 width: state.width, height: state.height,
349 bg: state.bg, fg: state.fg,
350 text: state.text, theme: state.theme,
351 accent: a.accentColor
352 });
353
354 canvas.style.width = maxPreviewW + 'px';
355 canvas.style.height = previewH + 'px';
356
357 dimsEl.textContent = state.width + ' × ' + state.height + ' px';
358 }
359
360 /* Event listeners */
361 [wInput, hInput, textInput].forEach(function (inp) {
362 inp.addEventListener('input', function () {
363 var wv = parseInt(wInput.value) || 1;
364 var hv = parseInt(hInput.value) || 1;
365 state.width = Math.max(1, Math.min(wv, 4000));
366 state.height = Math.max(1, Math.min(hv, 4000));
367 state.text = textInput.value;
368 render();
369 });
370 });
371
372 bgInput.addEventListener('input', function () { state.bg = bgInput.value; render(); });
373 fgInput.addEventListener('input', function () { state.fg = fgInput.value; render(); });
374
375 render();
376 }
377
378 function init() {
379 document.querySelectorAll('.bkbg-iph-app').forEach(initBlock);
380 }
381
382 if (document.readyState === 'loading') {
383 document.addEventListener('DOMContentLoaded', init);
384 } else {
385 init();
386 }
387 })();
388