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 / recipe-scaler / frontend.js

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

235 lines 12.5 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 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(el, obj, prefix) {
13 if (!obj || typeof obj !== 'object') return;
14 Object.keys(_typoKeys).forEach(function(k) {
15 var v = obj[k];
16 if (v === undefined || v === '' || v === null) return;
17 if (k === 'sizeDesktop' || k === 'sizeTablet' || k === 'sizeMobile') v = v + (obj.sizeUnit || 'px');
18 else if (k === 'lineHeightDesktop' || k === 'lineHeightTablet' || k === 'lineHeightMobile') v = v + (obj.lineHeightUnit || '');
19 else if (k === 'letterSpacingDesktop' || k === 'letterSpacingTablet' || k === 'letterSpacingMobile') v = v + (obj.letterSpacingUnit || 'px');
20 else if (k === 'wordSpacingDesktop' || k === 'wordSpacingTablet' || k === 'wordSpacingMobile') v = v + (obj.wordSpacingUnit || 'px');
21 el.style.setProperty(prefix + _typoKeys[k], String(v));
22 });
23 }
24
25 var SAMPLE_INGREDIENTS = [
26 { qty: 2, unit: 'cups', name: 'All-purpose flour' },
27 { qty: 2, unit: 'tsp', name: 'Baking powder' },
28 { qty: 0.5, unit: 'tsp', name: 'Salt' },
29 { qty: 2, unit: 'tbsp', name: 'Sugar' },
30 { qty: 2, unit: '', name: 'Large eggs' },
31 { qty: 1.5, unit: 'cups', name: 'Milk' },
32 { qty: 3, unit: 'tbsp', name: 'Butter, melted' }
33 ];
34
35 var QUICK_BTNS = [1, 2, 4, 6, 8, 12];
36
37 function fmtQty(n) {
38 if (n <= 0) return '0';
39 var fracs = [[0.125,'�
40 �'],[0.25,'¼'],[0.33,'�
41 �'],[0.5,'½'],[0.67,'�
42 �'],[0.75,'¾']];
43 var whole = Math.floor(n);
44 var rem = Math.round((n - whole)*1000)/1000;
45 for (var i=0; i<fracs.length; i++) {
46 if (Math.abs(rem - fracs[i][0]) < 0.02) {
47 return (whole > 0 ? whole + ' ' : '') + fracs[i][1];
48 }
49 }
50 if (rem === 0) return String(whole);
51 var d = Math.round(n * 100) / 100;
52 return d.toFixed(2).replace(/\.?0+$/, '');
53 }
54
55 function escHtml(s) {
56 return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
57 }
58
59 document.querySelectorAll('.bkbg-rsc-app').forEach(function(root) {
60 var opts;
61 try { opts = JSON.parse(root.getAttribute('data-opts') || '{}'); }
62 catch(e) { return; }
63
64 var o = {
65 title: opts.title || 'Recipe Scaler',
66 subtitle: opts.subtitle || '',
67 recipeName: opts.recipeName || 'Recipe',
68 defaultServings: parseInt(opts.defaultServings) || 4,
69 maxServings: parseInt(opts.maxServings) || 24,
70 showQuickBtns: opts.showQuickBtns !== false,
71 showNotes: opts.showNotes !== false,
72 recipeNotes: opts.recipeNotes || '',
73 accentColor: opts.accentColor || '#f97316',
74 sectionBg: opts.sectionBg || '#fff7ed',
75 cardBg: opts.cardBg || '#ffffff',
76 inputBg: opts.inputBg || '#fafafa',
77 ingredientBg: opts.ingredientBg || '#fef3c7',
78 titleColor: opts.titleColor || '#111827',
79 subtitleColor: opts.subtitleColor || '#6b7280',
80 labelColor: opts.labelColor || '#374151',
81 titleTypo: opts.titleTypo || {},
82 contentMaxWidth: parseInt(opts.contentMaxWidth) || 660
83 };
84
85 var baseServings = o.defaultServings;
86 var curServings = o.defaultServings;
87 var ingredients = SAMPLE_INGREDIENTS.map(function(i){ return { qty:i.qty, unit:i.unit, name:i.name }; });
88
89 // ── Build shell ────────────────────────────────────────────────
90 root.innerHTML =
91 '<div class="bkbg-rsc-wrap" style="background:' + o.sectionBg + ';max-width:' + o.contentMaxWidth + 'px;--rsc-accent:' + o.accentColor + '">' +
92 (o.title ? '<h3 class="bkbg-rsc-title" style="color:' + o.titleColor + '">' + o.title + '</h3>' : '') +
93 (o.subtitle ? '<p class="bkbg-rsc-subtitle" style="color:' + o.subtitleColor + '">' + o.subtitle + '</p>' : '') +
94
95 // Recipe name
96 '<div class="bkbg-rsc-name-card" style="background:' + o.cardBg + '">' +
97 '<span class="bkbg-rsc-name-icon">🍽️</span>' +
98 '<span class="bkbg-rsc-name-text" style="color:' + o.titleColor + '">' + escHtml(o.recipeName) + '</span>' +
99 '</div>' +
100
101 // Servings control
102 '<div class="bkbg-rsc-servings-card" style="background:' + o.cardBg + '">' +
103 '<div class="bkbg-rsc-servings-label" style="color:' + o.labelColor + '">Servings</div>' +
104 '<div class="bkbg-rsc-servings-row">' +
105 '<button class="bkbg-rsc-sv-btn" id="rsc-minus">−</button>' +
106 '<input class="bkbg-rsc-sv-input" id="rsc-sv-in" type="number" min="1" max="' + o.maxServings + '" value="' + curServings + '" style="background:' + o.inputBg + ';border-color:#e5e7eb">' +
107 '<button class="bkbg-rsc-sv-btn" id="rsc-plus">+</button>' +
108 '<span class="bkbg-rsc-sv-unit" style="color:' + o.labelColor + '">servings</span>' +
109 '</div>' +
110 (o.showQuickBtns ?
111 '<div class="bkbg-rsc-quick-row" id="rsc-quick">' +
112 QUICK_BTNS.map(function(n) {
113 return '<button class="bkbg-rsc-quick-btn' + (n===curServings?' active':'') + '" data-sv="' + n + '" style="color:' + (n===curServings?'#fff':o.labelColor) + '">' + n + 'x</button>';
114 }).join('') +
115 '</div>'
116 : '') +
117 '</div>' +
118
119 // Ingredients section
120 '<div class="bkbg-rsc-ing-section">' +
121 '<div class="bkbg-rsc-ing-heading" style="color:' + o.labelColor + '">Ingredients' +
122 '<span class="bkbg-rsc-scale-badge" id="rsc-scale-badge">×' + (curServings/baseServings).toFixed(2).replace(/\.?0+$/,'') + '</span>' +
123 '</div>' +
124 '<div class="bkbg-rsc-ing-list" id="rsc-ing-list"></div>' +
125 '</div>' +
126
127 // Add ingredient form
128 '<div class="bkbg-rsc-add-form">' +
129 '<input class="bkbg-rsc-add-input bkbg-rsc-add-qty" id="rsc-new-qty" type="number" placeholder="Qty" min="0" step="0.25" style="background:' + o.inputBg + ';color:' + o.labelColor + '">' +
130 '<input class="bkbg-rsc-add-input bkbg-rsc-add-unit" id="rsc-new-unit" type="text" placeholder="Unit (e.g. cups)" style="background:' + o.inputBg + ';color:' + o.labelColor + '">' +
131 '<input class="bkbg-rsc-add-input bkbg-rsc-add-name" id="rsc-new-name" type="text" placeholder="Ingredient name" style="background:' + o.inputBg + ';color:' + o.labelColor + '">' +
132 '<button id="rsc-add-btn" class="bkbg-rsc-add-btn">+ Add</button>' +
133 '</div>' +
134
135 // Notes
136 (o.showNotes && o.recipeNotes ?
137 '<div class="bkbg-rsc-notes">' +
138 '<div class="bkbg-rsc-notes-title">📝 Recipe Notes</div>' +
139 '<div class="bkbg-rsc-notes-text">' + escHtml(o.recipeNotes) + '</div>' +
140 '</div>'
141 : '') +
142 '</div>';
143
144 var wrap = root.querySelector('.bkbg-rsc-wrap');
145 if (wrap) typoCssVarsForEl(wrap, o.titleTypo || {}, '--bkrsc-tt-');
146
147 var svInput = root.querySelector('#rsc-sv-in');
148 var minusBtn = root.querySelector('#rsc-minus');
149 var plusBtn = root.querySelector('#rsc-plus');
150 var ingList = root.querySelector('#rsc-ing-list');
151 var badge = root.querySelector('#rsc-scale-badge');
152 var quickRow = root.querySelector('#rsc-quick');
153 var newQty = root.querySelector('#rsc-new-qty');
154 var newUnit = root.querySelector('#rsc-new-unit');
155 var newName = root.querySelector('#rsc-new-name');
156 var addBtn = root.querySelector('#rsc-add-btn');
157
158 // ── Re-render ingredients ──────────────────────────────────────
159 function renderIngredients() {
160 var factor = curServings / baseServings;
161 ingList.innerHTML = ingredients.map(function(ing, idx) {
162 var scaled = fmtQty(ing.qty * factor);
163 return '<div class="bkbg-rsc-ing-row" style="background:' + o.ingredientBg + '">' +
164 '<span class="bkbg-rsc-ing-qty">' + scaled + '</span>' +
165 '<span class="bkbg-rsc-ing-unit">' + escHtml(ing.unit) + '</span>' +
166 '<span class="bkbg-rsc-ing-name" style="color:' + o.labelColor + '">' + escHtml(ing.name) + '</span>' +
167 '<button class="bkbg-rsc-ing-del" data-idx="' + idx + '" title="Remove">✕</button>' +
168 '</div>';
169 }).join('');
170
171 ingList.querySelectorAll('.bkbg-rsc-ing-del').forEach(function(btn) {
172 btn.addEventListener('click', function() {
173 ingredients.splice(parseInt(btn.getAttribute('data-idx')), 1);
174 renderIngredients();
175 });
176 });
177
178 if (badge) {
179 var f = curServings / baseServings;
180 badge.textContent = '×' + (f === 1 ? '1' : f.toFixed(2).replace(/\.?0+$/, ''));
181 }
182 updateQuickBtns();
183 }
184
185 // ── Update servings ────────────────────────────────────────────
186 function setServings(n) {
187 n = Math.max(1, Math.min(o.maxServings, Math.round(n)));
188 curServings = n;
189 if (svInput) svInput.value = n;
190 renderIngredients();
191 }
192
193 function updateQuickBtns() {
194 if (!quickRow) return;
195 quickRow.querySelectorAll('.bkbg-rsc-quick-btn').forEach(function(b) {
196 var act = parseInt(b.getAttribute('data-sv')) === curServings;
197 b.classList.toggle('active', act);
198 b.style.color = act ? '#fff' : o.labelColor;
199 });
200 }
201
202 // ── Bindings ───────────────────────────────────────────────────
203 if (minusBtn) minusBtn.addEventListener('click', function() { setServings(curServings - 1); });
204 if (plusBtn) plusBtn.addEventListener('click', function() { setServings(curServings + 1); });
205 if (svInput) svInput.addEventListener('input', function() {
206 var v = parseInt(svInput.value);
207 if (!isNaN(v) && v > 0) { curServings = Math.min(o.maxServings, v); renderIngredients(); }
208 });
209
210 if (quickRow) {
211 quickRow.querySelectorAll('.bkbg-rsc-quick-btn').forEach(function(b) {
212 b.addEventListener('click', function() { setServings(parseInt(b.getAttribute('data-sv'))); });
213 });
214 }
215
216 if (addBtn) {
217 addBtn.addEventListener('click', function() {
218 var qty = parseFloat(newQty ? newQty.value : 0) || 0;
219 var unit = newUnit ? newUnit.value.trim() : '';
220 var name = newName ? newName.value.trim() : '';
221 if (!name) { if (newName) newName.focus(); return; }
222 // Store qty relative to base servings
223 ingredients.push({ qty: qty * (baseServings / curServings), unit: unit, name: name });
224 if (newQty) newQty.value = '';
225 if (newUnit) newUnit.value = '';
226 if (newName) newName.value = '';
227 renderIngredients();
228 });
229 }
230
231 // ── Initial render ─────────────────────────────────────────────
232 renderIngredients();
233 });
234 })();
235