| 1 |
( function () { |
| 2 |
var el = wp.element.createElement; |
| 3 |
var Fragment = wp.element.Fragment; |
| 4 |
var useState = wp.element.useState; |
| 5 |
var __ = wp.i18n.__; |
| 6 |
var registerBlockType = wp.blocks.registerBlockType; |
| 7 |
var InspectorControls = wp.blockEditor.InspectorControls; |
| 8 |
var useBlockProps = wp.blockEditor.useBlockProps; |
| 9 |
var RichText = wp.blockEditor.RichText; |
| 10 |
var PanelBody = wp.components.PanelBody; |
| 11 |
var Button = wp.components.Button; |
| 12 |
var ToggleControl = wp.components.ToggleControl; |
| 13 |
var RangeControl = wp.components.RangeControl; |
| 14 |
var SelectControl = wp.components.SelectControl; |
| 15 |
var TextControl = wp.components.TextControl; |
| 16 |
var ColorPicker = wp.components.ColorPicker; |
| 17 |
var Popover = wp.components.Popover; |
| 18 |
|
| 19 |
var _tc; function getTypoControl() { return _tc || (_tc = window.bkbgTypographyControl); } |
| 20 |
var _tv; function getTypoCssVars() { return _tv || (_tv = window.bkbgTypoCssVars); } |
| 21 |
|
| 22 |
function buildTypoVars(a) { |
| 23 |
var fn = getTypoCssVars(); |
| 24 |
if (!fn) return {}; |
| 25 |
var v = {}; |
| 26 |
Object.assign(v, fn(a.nameTypo || {}, '--bkbg-pt-nm-')); |
| 27 |
Object.assign(v, fn(a.descTypo || {}, '--bkbg-pt-ds-')); |
| 28 |
Object.assign(v, fn(a.priceTypo || {}, '--bkbg-pt-pr-')); |
| 29 |
Object.assign(v, fn(a.featureTypo || {}, '--bkbg-pt-ft-')); |
| 30 |
Object.assign(v, fn(a.btnTypo || {}, '--bkbg-pt-bt-')); |
| 31 |
return v; |
| 32 |
} |
| 33 |
|
| 34 |
// ── CSS vars ────────────────────────────────────────────────────────────── |
| 35 |
function buildWrapStyle(a) { |
| 36 |
var shadow = a.cardStyle === 'shadow' ? '0 8px 32px rgba(0,0,0,0.10)' : |
| 37 |
a.cardStyle === 'shadow-lg' ? '0 20px 60px rgba(0,0,0,0.16)' : |
| 38 |
a.cardStyle === 'flat' ? 'none' : 'none'; |
| 39 |
return { |
| 40 |
'--bkbg-pt-accent' : a.accentColor, |
| 41 |
'--bkbg-pt-accent2' : a.accentColorSecondary, |
| 42 |
'--bkbg-pt-card-bg' : a.cardBg, |
| 43 |
'--bkbg-pt-card-border-color' : a.cardBorderColor, |
| 44 |
'--bkbg-pt-card-border-w' : a.cardBorderWidth + 'px', |
| 45 |
'--bkbg-pt-card-radius' : a.cardBorderRadius + 'px', |
| 46 |
'--bkbg-pt-card-pad' : a.cardPaddingTop + 'px ' + a.cardPaddingRight + 'px ' + a.cardPaddingBottom + 'px ' + a.cardPaddingLeft + 'px', |
| 47 |
'--bkbg-pt-card-shadow' : shadow, |
| 48 |
'--bkbg-pt-plan-name-color' : a.planNameColor, |
| 49 |
'--bkbg-pt-desc-color' : a.descriptionColor, |
| 50 |
'--bkbg-pt-price-color' : a.priceColor, |
| 51 |
'--bkbg-pt-currency-size' : a.currencySize + 'px', |
| 52 |
'--bkbg-pt-period-size' : a.periodSize + 'px', |
| 53 |
'--bkbg-pt-period-color' : a.periodColor, |
| 54 |
'--bkbg-pt-subtext-size' : a.priceSubtextSize + 'px', |
| 55 |
'--bkbg-pt-subtext-color' : a.priceSubtextColor, |
| 56 |
'--bkbg-pt-feature-color' : a.featureColor, |
| 57 |
'--bkbg-pt-feature-excl-color': a.featureExcludedColor, |
| 58 |
'--bkbg-pt-feature-incl-icon-color': a.featureIncludedIconColor, |
| 59 |
'--bkbg-pt-feature-excl-icon-color': a.featureExcludedIconColor, |
| 60 |
'--bkbg-pt-feature-gap' : a.featureGap + 'px', |
| 61 |
'--bkbg-pt-btn-bg' : a.btnBg, |
| 62 |
'--bkbg-pt-btn-color' : a.btnColor, |
| 63 |
'--bkbg-pt-btn-bg-hover' : a.btnBgHover, |
| 64 |
'--bkbg-pt-btn-color-hover' : a.btnColorHover, |
| 65 |
'--bkbg-pt-btn-border-color' : a.btnBorderColor, |
| 66 |
'--bkbg-pt-btn-border-w' : a.btnBorderWidth + 'px', |
| 67 |
'--bkbg-pt-btn-radius' : a.btnBorderRadius + 'px', |
| 68 |
'--bkbg-pt-btn-pad-v' : a.btnPaddingV + 'px', |
| 69 |
'--bkbg-pt-badge-bg' : a.badgeBg, |
| 70 |
'--bkbg-pt-badge-color' : a.badgeColor, |
| 71 |
'--bkbg-pt-badge-size' : a.badgeSize + 'px', |
| 72 |
'--bkbg-pt-badge-radius' : a.badgeBorderRadius + 'px', |
| 73 |
'--bkbg-pt-header-bg' : a.headerBg, |
| 74 |
'--bkbg-pt-section-gap' : a.sectionGap + 'px' |
| 75 |
}; |
| 76 |
} |
| 77 |
|
| 78 |
// ── Color swatch ────────────────────────────────────────────────────────── |
| 79 |
function renderColorControl(key, label, value, onChange, openKey, setOpenKey) { |
| 80 |
var isOpen = openKey === key; |
| 81 |
return el('div', { key: key, style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '4px 0', gap: '8px' } }, |
| 82 |
el('span', { style: { fontSize: '12px', color: '#1e1e1e', flex: 1, lineHeight: 1.4 } }, label), |
| 83 |
el('div', { style: { position: 'relative', flexShrink: 0 } }, |
| 84 |
el('button', { |
| 85 |
type: 'button', |
| 86 |
title: value || 'none', |
| 87 |
onClick: function () { setOpenKey(isOpen ? null : key); }, |
| 88 |
style: { |
| 89 |
width: '28px', height: '28px', borderRadius: '4px', |
| 90 |
border: isOpen ? '2px solid #007cba' : '2px solid #ddd', |
| 91 |
cursor: 'pointer', padding: 0, display: 'block', |
| 92 |
background: value || '#fff' |
| 93 |
} |
| 94 |
}), |
| 95 |
isOpen && el(Popover, { position: 'bottom left', onClose: function () { setOpenKey(null); } }, |
| 96 |
el('div', { style: { padding: '8px' } }, |
| 97 |
el(ColorPicker, { color: value, enableAlpha: true, onChange: onChange }) |
| 98 |
) |
| 99 |
) |
| 100 |
) |
| 101 |
); |
| 102 |
} |
| 103 |
|
| 104 |
function updateFeature(features, index, field, value, setAttributes) { |
| 105 |
var newFeatures = features.map(function (f, i) { |
| 106 |
if (i !== index) return f; |
| 107 |
var updated = {}; |
| 108 |
for (var k in f) { updated[k] = f[k]; } |
| 109 |
updated[field] = value; |
| 110 |
return updated; |
| 111 |
}); |
| 112 |
setAttributes({ features: newFeatures }); |
| 113 |
} |
| 114 |
|
| 115 |
// ── Register ────────────────────────────────────────────────────────────── |
| 116 |
registerBlockType('blockenberg/pricing-table', { |
| 117 |
title: __('Pricing Table', 'blockenberg'), |
| 118 |
icon: 'products', |
| 119 |
category: 'bkbg-marketing', |
| 120 |
description: __('Pricing card with plan name, price, features list, and CTA button.', 'blockenberg'), |
| 121 |
|
| 122 |
edit: function (props) { |
| 123 |
var a = props.attributes; |
| 124 |
var setAttributes = props.setAttributes; |
| 125 |
|
| 126 |
var openColorKeyState = useState(null); |
| 127 |
var openColorKey = openColorKeyState[0]; |
| 128 |
var setOpenColorKey = openColorKeyState[1]; |
| 129 |
|
| 130 |
function cc(key, label, attrKey) { |
| 131 |
return renderColorControl(key, label, a[attrKey], function (val) { |
| 132 |
var upd = {}; upd[attrKey] = val; setAttributes(upd); |
| 133 |
}, openColorKey, setOpenColorKey); |
| 134 |
} |
| 135 |
|
| 136 |
function addFeature() { |
| 137 |
setAttributes({ |
| 138 |
features: a.features.concat([{ text: __('New feature', 'blockenberg'), included: true, icon: 'dashicons-yes-alt' }]) |
| 139 |
}); |
| 140 |
} |
| 141 |
|
| 142 |
function removeFeature(i) { |
| 143 |
setAttributes({ features: a.features.filter(function (_, idx) { return idx !== i; }) }); |
| 144 |
} |
| 145 |
|
| 146 |
function moveFeature(i, dir) { |
| 147 |
var ni = i + dir; |
| 148 |
if (ni < 0 || ni >= a.features.length) return; |
| 149 |
var arr = a.features.slice(); |
| 150 |
var tmp = arr[i]; arr[i] = arr[ni]; arr[ni] = tmp; |
| 151 |
setAttributes({ features: arr }); |
| 152 |
} |
| 153 |
|
| 154 |
var cardClass = 'bkbg-pt-card bkbg-pt-style-' + a.cardStyle + (a.isPopular ? ' is-popular' : '') + ' bkbg-pt-header-' + a.headerStyle; |
| 155 |
var blockProps = useBlockProps({ className: cardClass, style: Object.assign({}, buildWrapStyle(a), buildTypoVars(a)) }); |
| 156 |
|
| 157 |
// ── Inspector ───────────────────────────────────────────────────── |
| 158 |
var inspector = el(InspectorControls, {}, |
| 159 |
// Plan Info |
| 160 |
el(PanelBody, { title: __('Plan Info', 'blockenberg'), initialOpen: true }, |
| 161 |
el(TextControl, { |
| 162 |
label: __('Plan Name', 'blockenberg'), value: a.planName, |
| 163 |
onChange: function (v) { setAttributes({ planName: v }); } |
| 164 |
}), |
| 165 |
el(TextControl, { |
| 166 |
label: __('Description', 'blockenberg'), value: a.planDescription, |
| 167 |
onChange: function (v) { setAttributes({ planDescription: v }); } |
| 168 |
}), |
| 169 |
el(ToggleControl, { |
| 170 |
label: __('Show Description', 'blockenberg'), checked: a.showDescription, |
| 171 |
onChange: function (v) { setAttributes({ showDescription: v }); } |
| 172 |
}), |
| 173 |
el(ToggleControl, { |
| 174 |
label: __('Mark as Popular', 'blockenberg'), checked: a.isPopular, |
| 175 |
onChange: function (v) { setAttributes({ isPopular: v }); } |
| 176 |
}), |
| 177 |
a.isPopular && el(TextControl, { |
| 178 |
label: __('Popular Badge Label', 'blockenberg'), value: a.popularLabel, |
| 179 |
onChange: function (v) { setAttributes({ popularLabel: v }); } |
| 180 |
}) |
| 181 |
), |
| 182 |
|
| 183 |
// Pricing |
| 184 |
el(PanelBody, { title: __('Price', 'blockenberg'), initialOpen: false }, |
| 185 |
el(TextControl, { |
| 186 |
label: __('Currency Symbol', 'blockenberg'), value: a.currency, |
| 187 |
onChange: function (v) { setAttributes({ currency: v }); } |
| 188 |
}), |
| 189 |
el(SelectControl, { |
| 190 |
label: __('Currency Position', 'blockenberg'), value: a.currencyPosition, |
| 191 |
options: [{ label: __('Before', 'blockenberg'), value: 'before' }, { label: __('After', 'blockenberg'), value: 'after' }], |
| 192 |
onChange: function (v) { setAttributes({ currencyPosition: v }); } |
| 193 |
}), |
| 194 |
el(TextControl, { |
| 195 |
label: __('Price', 'blockenberg'), value: a.price, |
| 196 |
onChange: function (v) { setAttributes({ price: v }); } |
| 197 |
}), |
| 198 |
el(TextControl, { |
| 199 |
label: __('Period (e.g. /month)', 'blockenberg'), value: a.pricePeriod, |
| 200 |
onChange: function (v) { setAttributes({ pricePeriod: v }); } |
| 201 |
}), |
| 202 |
el(TextControl, { |
| 203 |
label: __('Price Subtext', 'blockenberg'), value: a.priceSubtext, |
| 204 |
onChange: function (v) { setAttributes({ priceSubtext: v }); } |
| 205 |
}), |
| 206 |
el(ToggleControl, { |
| 207 |
label: __('Show Price Subtext', 'blockenberg'), checked: a.showPriceSubtext, |
| 208 |
onChange: function (v) { setAttributes({ showPriceSubtext: v }); } |
| 209 |
}) |
| 210 |
), |
| 211 |
|
| 212 |
// Features |
| 213 |
el(PanelBody, { title: __('Features', 'blockenberg'), initialOpen: false }, |
| 214 |
el(SelectControl, { |
| 215 |
label: __('Feature List Style', 'blockenberg'), value: a.featureListStyle, |
| 216 |
options: [ |
| 217 |
{ label: __('Icons', 'blockenberg'), value: 'icon' }, |
| 218 |
{ label: __('Bullet', 'blockenberg'), value: 'bullet' }, |
| 219 |
{ label: __('Checkmark (CSS)', 'blockenberg'), value: 'check' }, |
| 220 |
{ label: __('None', 'blockenberg'), value: 'none' } |
| 221 |
], |
| 222 |
onChange: function (v) { setAttributes({ featureListStyle: v }); } |
| 223 |
}), |
| 224 |
el(ToggleControl, { |
| 225 |
label: __('Show Divider between Features and Button', 'blockenberg'), checked: a.showFeatureDivider, |
| 226 |
onChange: function (v) { setAttributes({ showFeatureDivider: v }); } |
| 227 |
}), |
| 228 |
el(RangeControl, { |
| 229 |
label: __('Gap Between Features (px)', 'blockenberg'), value: a.featureGap, min: 4, max: 32, |
| 230 |
onChange: function (v) { setAttributes({ featureGap: v }); } |
| 231 |
}), |
| 232 |
a.features.map(function (feat, i) { |
| 233 |
return el('div', { |
| 234 |
key: i, |
| 235 |
style: { border: '1px solid #e0e0e0', borderRadius: '6px', padding: '10px', marginBottom: '8px', background: '#fafafa' } |
| 236 |
}, |
| 237 |
el('div', { style: { display: 'flex', alignItems: 'center', gap: '4px', marginBottom: '8px' } }, |
| 238 |
el('span', { style: { fontSize: '11px', fontWeight: '600', flex: 1, color: feat.included ? '#15803d' : '#94a3b8' } }, |
| 239 |
(i + 1) + '. ' + (feat.included ? __('Included', 'blockenberg') : __('Excluded', 'blockenberg')) |
| 240 |
), |
| 241 |
el(Button, { isSmall: true, variant: 'tertiary', onClick: function () { moveFeature(i, -1); }, disabled: i === 0 }, '↑'), |
| 242 |
el(Button, { isSmall: true, variant: 'tertiary', onClick: function () { moveFeature(i, 1); }, disabled: i === a.features.length - 1 }, '↓'), |
| 243 |
el(Button, { isSmall: true, isDestructive: true, variant: 'tertiary', onClick: function () { removeFeature(i); } }, '✕') |
| 244 |
), |
| 245 |
el(TextControl, { |
| 246 |
label: __('Feature Text', 'blockenberg'), value: feat.text, |
| 247 |
onChange: function (v) { updateFeature(a.features, i, 'text', v, setAttributes); } |
| 248 |
}), |
| 249 |
el(ToggleControl, { |
| 250 |
label: __('Included', 'blockenberg'), checked: feat.included, |
| 251 |
onChange: function (v) { |
| 252 |
updateFeature(a.features, i, 'included', v, setAttributes); |
| 253 |
updateFeature(a.features, i, 'icon', v ? 'dashicons-yes-alt' : 'dashicons-no-alt', setAttributes); |
| 254 |
} |
| 255 |
}) |
| 256 |
); |
| 257 |
}), |
| 258 |
el(Button, { variant: 'secondary', onClick: addFeature, style: { width: '100%', justifyContent: 'center' } }, |
| 259 |
'+ ' + __('Add Feature', 'blockenberg') |
| 260 |
) |
| 261 |
), |
| 262 |
|
| 263 |
// Button |
| 264 |
el(PanelBody, { title: __('Button', 'blockenberg'), initialOpen: false }, |
| 265 |
el(TextControl, { |
| 266 |
label: __('Button Text', 'blockenberg'), value: a.buttonText, |
| 267 |
onChange: function (v) { setAttributes({ buttonText: v }); } |
| 268 |
}), |
| 269 |
el(TextControl, { |
| 270 |
label: __('Button URL', 'blockenberg'), value: a.buttonUrl, |
| 271 |
onChange: function (v) { setAttributes({ buttonUrl: v }); } |
| 272 |
}), |
| 273 |
el(ToggleControl, { |
| 274 |
label: __('Open in New Tab', 'blockenberg'), checked: a.buttonTarget, |
| 275 |
onChange: function (v) { setAttributes({ buttonTarget: v }); } |
| 276 |
}), |
| 277 |
el(SelectControl, { |
| 278 |
label: __('Button Width', 'blockenberg'), value: a.btnWidth, |
| 279 |
options: [ |
| 280 |
{ label: __('Full Width', 'blockenberg'), value: 'full' }, |
| 281 |
{ label: __('Auto', 'blockenberg'), value: 'auto' } |
| 282 |
], |
| 283 |
onChange: function (v) { setAttributes({ btnWidth: v }); } |
| 284 |
}), |
| 285 |
el(RangeControl, { |
| 286 |
label: __('Vertical Padding (px)', 'blockenberg'), value: a.btnPaddingV, min: 8, max: 32, |
| 287 |
onChange: function (v) { setAttributes({ btnPaddingV: v }); } |
| 288 |
}), |
| 289 |
el(RangeControl, { |
| 290 |
label: __('Border Width (px)', 'blockenberg'), value: a.btnBorderWidth, min: 0, max: 6, |
| 291 |
onChange: function (v) { setAttributes({ btnBorderWidth: v }); } |
| 292 |
}), |
| 293 |
el(RangeControl, { |
| 294 |
label: __('Border Radius (px)', 'blockenberg'), value: a.btnBorderRadius, min: 0, max: 60, |
| 295 |
onChange: function (v) { setAttributes({ btnBorderRadius: v }); } |
| 296 |
}), |
| 297 |
cc('btnBg', __('Button Background', 'blockenberg'), 'btnBg'), |
| 298 |
cc('btnColor', __('Button Text Color', 'blockenberg'), 'btnColor'), |
| 299 |
cc('btnBgHover', __('Button Hover Background', 'blockenberg'), 'btnBgHover'), |
| 300 |
cc('btnColorHover', __('Button Hover Text Color', 'blockenberg'), 'btnColorHover'), |
| 301 |
cc('btnBorderColor', __('Button Border Color', 'blockenberg'), 'btnBorderColor') |
| 302 |
), |
| 303 |
|
| 304 |
// Card design |
| 305 |
el(PanelBody, { title: __('Card Design', 'blockenberg'), initialOpen: false }, |
| 306 |
el(SelectControl, { |
| 307 |
label: __('Card Style', 'blockenberg'), value: a.cardStyle, |
| 308 |
options: [ |
| 309 |
{ label: __('Shadow', 'blockenberg'), value: 'shadow' }, |
| 310 |
{ label: __('Large Shadow', 'blockenberg'), value: 'shadow-lg' }, |
| 311 |
{ label: __('Flat + Border', 'blockenberg'), value: 'flat' }, |
| 312 |
{ label: __('Gradient Header', 'blockenberg'), value: 'gradient' }, |
| 313 |
{ label: __('Colored Header', 'blockenberg'), value: 'colored-header' } |
| 314 |
], |
| 315 |
onChange: function (v) { setAttributes({ cardStyle: v }); } |
| 316 |
}), |
| 317 |
(a.cardStyle === 'colored-header' || a.cardStyle === 'gradient') && el(SelectControl, { |
| 318 |
label: __('Header Style', 'blockenberg'), value: a.headerStyle, |
| 319 |
options: [ |
| 320 |
{ label: __('Accent Color', 'blockenberg'), value: 'accent' }, |
| 321 |
{ label: __('Dark', 'blockenberg'), value: 'dark' }, |
| 322 |
{ label: __('Light', 'blockenberg'), value: 'light' } |
| 323 |
], |
| 324 |
onChange: function (v) { setAttributes({ headerStyle: v }); } |
| 325 |
}), |
| 326 |
el(RangeControl, { |
| 327 |
label: __('Border Radius (px)', 'blockenberg'), value: a.cardBorderRadius, min: 0, max: 40, |
| 328 |
onChange: function (v) { setAttributes({ cardBorderRadius: v }); } |
| 329 |
}), |
| 330 |
el(RangeControl, { |
| 331 |
label: __('Border Width (px)', 'blockenberg'), value: a.cardBorderWidth, min: 0, max: 6, |
| 332 |
onChange: function (v) { setAttributes({ cardBorderWidth: v }); } |
| 333 |
}), |
| 334 |
el(RangeControl, { |
| 335 |
label: __('Section Gap (px)', 'blockenberg'), value: a.sectionGap, min: 8, max: 60, |
| 336 |
onChange: function (v) { setAttributes({ sectionGap: v }); } |
| 337 |
}), |
| 338 |
el(RangeControl, { |
| 339 |
label: __('Padding Top (px)', 'blockenberg'), value: a.cardPaddingTop, min: 0, max: 80, |
| 340 |
onChange: function (v) { setAttributes({ cardPaddingTop: v }); } |
| 341 |
}), |
| 342 |
el(RangeControl, { |
| 343 |
label: __('Padding Right (px)', 'blockenberg'), value: a.cardPaddingRight, min: 0, max: 80, |
| 344 |
onChange: function (v) { setAttributes({ cardPaddingRight: v }); } |
| 345 |
}), |
| 346 |
el(RangeControl, { |
| 347 |
label: __('Padding Bottom (px)', 'blockenberg'), value: a.cardPaddingBottom, min: 0, max: 80, |
| 348 |
onChange: function (v) { setAttributes({ cardPaddingBottom: v }); } |
| 349 |
}), |
| 350 |
el(RangeControl, { |
| 351 |
label: __('Padding Left (px)', 'blockenberg'), value: a.cardPaddingLeft, min: 0, max: 80, |
| 352 |
onChange: function (v) { setAttributes({ cardPaddingLeft: v }); } |
| 353 |
}), |
| 354 |
cc('accentColor', __('Accent Color', 'blockenberg'), 'accentColor'), |
| 355 |
cc('accentColorSecondary', __('Accent Color 2 (gradient end)', 'blockenberg'), 'accentColorSecondary'), |
| 356 |
cc('cardBg', __('Card Background', 'blockenberg'), 'cardBg'), |
| 357 |
cc('cardBorderColor', __('Card Border Color', 'blockenberg'), 'cardBorderColor'), |
| 358 |
cc('headerBg', __('Header Background', 'blockenberg'), 'headerBg') |
| 359 |
), |
| 360 |
|
| 361 |
// Typography |
| 362 |
el(PanelBody, { title: __('Typography', 'blockenberg'), initialOpen: false }, |
| 363 |
(function () { |
| 364 |
var TC = getTypoControl(); |
| 365 |
if (!TC) return el('p', null, 'Loading\u2026'); |
| 366 |
return el(Fragment, null, |
| 367 |
el(TC, { label: 'Plan Name', value: a.nameTypo, onChange: function (v) { setAttributes({ nameTypo: v }); } }), |
| 368 |
el(TC, { label: 'Description', value: a.descTypo, onChange: function (v) { setAttributes({ descTypo: v }); } }), |
| 369 |
el(TC, { label: 'Price', value: a.priceTypo, onChange: function (v) { setAttributes({ priceTypo: v }); } }), |
| 370 |
el(TC, { label: 'Feature', value: a.featureTypo, onChange: function (v) { setAttributes({ featureTypo: v }); } }), |
| 371 |
el(TC, { label: 'Button', value: a.btnTypo, onChange: function (v) { setAttributes({ btnTypo: v }); } }) |
| 372 |
); |
| 373 |
})(), |
| 374 |
el(RangeControl, { label: __('Currency Size (px)', 'blockenberg'), value: a.currencySize, min: 12, max: 48, onChange: function (v) { setAttributes({ currencySize: v }); } }), |
| 375 |
el(RangeControl, { label: __('Period Size (px)', 'blockenberg'), value: a.periodSize, min: 11, max: 24, onChange: function (v) { setAttributes({ periodSize: v }); } }), |
| 376 |
el(RangeControl, { label: __('Subtext Size (px)', 'blockenberg'), value: a.priceSubtextSize, min: 10, max: 18, onChange: function (v) { setAttributes({ priceSubtextSize: v }); } }), |
| 377 |
el(RangeControl, { label: __('Badge Size (px)', 'blockenberg'), value: a.badgeSize, min: 10, max: 18, onChange: function (v) { setAttributes({ badgeSize: v }); } }), |
| 378 |
cc('planNameColor', __('Plan Name Color', 'blockenberg'), 'planNameColor'), |
| 379 |
cc('descriptionColor', __('Description Color', 'blockenberg'), 'descriptionColor'), |
| 380 |
cc('priceColor', __('Price Color', 'blockenberg'), 'priceColor'), |
| 381 |
cc('periodColor', __('Period Color', 'blockenberg'), 'periodColor'), |
| 382 |
cc('priceSubtextColor', __('Subtext Color', 'blockenberg'), 'priceSubtextColor'), |
| 383 |
cc('featureColor', __('Feature (included) Color', 'blockenberg'), 'featureColor'), |
| 384 |
cc('featureExcludedColor', __('Feature (excluded) Color', 'blockenberg'), 'featureExcludedColor'), |
| 385 |
cc('featureIncludedIconColor', __('Included Icon Color', 'blockenberg'), 'featureIncludedIconColor'), |
| 386 |
cc('featureExcludedIconColor', __('Excluded Icon Color', 'blockenberg'), 'featureExcludedIconColor') |
| 387 |
), |
| 388 |
|
| 389 |
// Badge |
| 390 |
el(PanelBody, { title: __('Popular Badge', 'blockenberg'), initialOpen: false }, |
| 391 |
el(RangeControl, { label: __('Border Radius (px)', 'blockenberg'), value: a.badgeBorderRadius, min: 0, max: 50, onChange: function (v) { setAttributes({ badgeBorderRadius: v }); } }), |
| 392 |
cc('badgeBg', __('Badge Background', 'blockenberg'), 'badgeBg'), |
| 393 |
cc('badgeColor', __('Badge Text Color', 'blockenberg'), 'badgeColor') |
| 394 |
) |
| 395 |
); |
| 396 |
|
| 397 |
// ── Preview render ───────────────────────────────────────────────── |
| 398 |
function renderFeatureIcon(feat) { |
| 399 |
if (a.featureListStyle === 'none') return null; |
| 400 |
if (a.featureListStyle === 'bullet') return el('span', { className: 'bkbg-pt-feat-bullet' }); |
| 401 |
if (a.featureListStyle === 'check') return el('span', { className: 'bkbg-pt-feat-check' + (feat.included ? ' is-included' : ' is-excluded') }); |
| 402 |
// dashicons |
| 403 |
var icon = feat.included ? 'dashicons-yes-alt' : 'dashicons-no-alt'; |
| 404 |
return el('span', { className: 'dashicons ' + icon + ' bkbg-pt-feat-icon' + (feat.included ? ' is-included' : ' is-excluded') }); |
| 405 |
} |
| 406 |
|
| 407 |
function renderPriceArea() { |
| 408 |
var currencyEl = el('span', { className: 'bkbg-pt-currency' }, a.currency); |
| 409 |
var priceEl = el('span', { className: 'bkbg-pt-price-amount' }, a.price); |
| 410 |
var periodEl = el('span', { className: 'bkbg-pt-period' }, a.pricePeriod); |
| 411 |
return el('div', { className: 'bkbg-pt-price-row' }, |
| 412 |
el('div', { className: 'bkbg-pt-price-main' }, |
| 413 |
a.currencyPosition === 'before' ? el(Fragment, {}, currencyEl, priceEl) : el(Fragment, {}, priceEl, currencyEl), |
| 414 |
periodEl |
| 415 |
), |
| 416 |
a.showPriceSubtext && a.priceSubtext && el('div', { className: 'bkbg-pt-subtext' }, a.priceSubtext) |
| 417 |
); |
| 418 |
} |
| 419 |
|
| 420 |
var headerChildren = [ |
| 421 |
a.isPopular && el('div', { key: 'badge', className: 'bkbg-pt-badge' }, a.popularLabel), |
| 422 |
el(RichText, { |
| 423 |
key: 'name', tagName: 'div', className: 'bkbg-pt-plan-name', |
| 424 |
value: a.planName, onChange: function (v) { setAttributes({ planName: v }); } |
| 425 |
}), |
| 426 |
a.showDescription && el(RichText, { |
| 427 |
key: 'desc', tagName: 'div', className: 'bkbg-pt-description', |
| 428 |
value: a.planDescription, onChange: function (v) { setAttributes({ planDescription: v }); }, |
| 429 |
placeholder: __('Plan description…', 'blockenberg') |
| 430 |
}) |
| 431 |
]; |
| 432 |
|
| 433 |
return el(Fragment, {}, |
| 434 |
inspector, |
| 435 |
el('div', blockProps, |
| 436 |
el('div', { className: 'bkbg-pt-header' }, headerChildren), |
| 437 |
el('div', { className: 'bkbg-pt-pricing' }, renderPriceArea()), |
| 438 |
el('ul', { className: 'bkbg-pt-features bkbg-pt-feat-style-' + a.featureListStyle }, |
| 439 |
a.features.map(function (feat, i) { |
| 440 |
return el('li', { key: i, className: 'bkbg-pt-feat-item' + (feat.included ? ' is-included' : ' is-excluded') }, |
| 441 |
renderFeatureIcon(feat), |
| 442 |
el('span', { className: 'bkbg-pt-feat-text' }, feat.text) |
| 443 |
); |
| 444 |
}) |
| 445 |
), |
| 446 |
a.showFeatureDivider && el('hr', { className: 'bkbg-pt-divider' }), |
| 447 |
el('div', { className: 'bkbg-pt-cta' }, |
| 448 |
el('a', { |
| 449 |
href: a.buttonUrl || '#', |
| 450 |
className: 'bkbg-pt-btn bkbg-pt-btn-' + a.btnWidth, |
| 451 |
onClick: function (e) { e.preventDefault(); } |
| 452 |
}, a.buttonText) |
| 453 |
) |
| 454 |
) |
| 455 |
); |
| 456 |
}, |
| 457 |
|
| 458 |
// ── Save ───────────────────────────────────────────────────────────── |
| 459 |
save: function (props) { |
| 460 |
var a = props.attributes; |
| 461 |
var cardClass = 'bkbg-pt-card bkbg-pt-style-' + a.cardStyle + (a.isPopular ? ' is-popular' : '') + ' bkbg-pt-header-' + a.headerStyle; |
| 462 |
|
| 463 |
function saveFeatureIcon(feat) { |
| 464 |
if (a.featureListStyle === 'none') return null; |
| 465 |
if (a.featureListStyle === 'bullet') return el('span', { className: 'bkbg-pt-feat-bullet', 'aria-hidden': 'true' }); |
| 466 |
if (a.featureListStyle === 'check') return el('span', { className: 'bkbg-pt-feat-check' + (feat.included ? ' is-included' : ' is-excluded'), 'aria-hidden': 'true' }); |
| 467 |
var icon = feat.included ? 'dashicons-yes-alt' : 'dashicons-no-alt'; |
| 468 |
return el('span', { className: 'dashicons ' + icon + ' bkbg-pt-feat-icon' + (feat.included ? ' is-included' : ' is-excluded'), 'aria-hidden': 'true' }); |
| 469 |
} |
| 470 |
|
| 471 |
var currencyEl = el('span', { className: 'bkbg-pt-currency' }, a.currency); |
| 472 |
var priceEl = el('span', { className: 'bkbg-pt-price-amount' }, a.price); |
| 473 |
var periodEl = el('span', { className: 'bkbg-pt-period' }, a.pricePeriod); |
| 474 |
|
| 475 |
return el('div', wp.blockEditor.useBlockProps.save({ className: cardClass, style: Object.assign({}, buildWrapStyle(a), buildTypoVars(a)) }), |
| 476 |
el('div', { className: 'bkbg-pt-header' }, |
| 477 |
a.isPopular && el('div', { className: 'bkbg-pt-badge' }, a.popularLabel), |
| 478 |
el(RichText.Content, { tagName: 'div', className: 'bkbg-pt-plan-name', value: a.planName }), |
| 479 |
a.showDescription && el(RichText.Content, { tagName: 'div', className: 'bkbg-pt-description', value: a.planDescription }) |
| 480 |
), |
| 481 |
el('div', { className: 'bkbg-pt-pricing' }, |
| 482 |
el('div', { className: 'bkbg-pt-price-row' }, |
| 483 |
el('div', { className: 'bkbg-pt-price-main' }, |
| 484 |
a.currencyPosition === 'before' ? el(Fragment, {}, currencyEl, priceEl) : el(Fragment, {}, priceEl, currencyEl), |
| 485 |
periodEl |
| 486 |
), |
| 487 |
a.showPriceSubtext && a.priceSubtext && el('div', { className: 'bkbg-pt-subtext' }, a.priceSubtext) |
| 488 |
) |
| 489 |
), |
| 490 |
el('ul', { className: 'bkbg-pt-features bkbg-pt-feat-style-' + a.featureListStyle }, |
| 491 |
a.features.map(function (feat, i) { |
| 492 |
return el('li', { key: i, className: 'bkbg-pt-feat-item' + (feat.included ? ' is-included' : ' is-excluded') }, |
| 493 |
saveFeatureIcon(feat), |
| 494 |
el('span', { className: 'bkbg-pt-feat-text' }, feat.text) |
| 495 |
); |
| 496 |
}) |
| 497 |
), |
| 498 |
a.showFeatureDivider && el('hr', { className: 'bkbg-pt-divider' }), |
| 499 |
el('div', { className: 'bkbg-pt-cta' }, |
| 500 |
el('a', { |
| 501 |
href: a.buttonUrl || '#', |
| 502 |
className: 'bkbg-pt-btn bkbg-pt-btn-' + a.btnWidth, |
| 503 |
target: a.buttonTarget ? '_blank' : undefined, |
| 504 |
rel: a.buttonTarget ? 'noopener noreferrer' : undefined |
| 505 |
}, a.buttonText) |
| 506 |
) |
| 507 |
); |
| 508 |
}, |
| 509 |
|
| 510 |
deprecated: [{ |
| 511 |
attributes: { |
| 512 |
planName: { type: "string", default: "Professional" }, |
| 513 |
planDescription: { type: "string", default: "Perfect for growing teams and projects" }, |
| 514 |
currency: { type: "string", default: "$" }, |
| 515 |
currencyPosition: { type: "string", default: "before" }, |
| 516 |
price: { type: "string", default: "49" }, |
| 517 |
pricePeriod: { type: "string", default: "/month" }, |
| 518 |
priceSubtext: { type: "string", default: "Billed annually" }, |
| 519 |
features: { type: "array", default: [ |
| 520 |
{ text: "Unlimited projects", included: true, icon: "dashicons-yes-alt" }, |
| 521 |
{ text: "Advanced analytics", included: true, icon: "dashicons-yes-alt" }, |
| 522 |
{ text: "Priority support", included: true, icon: "dashicons-yes-alt" }, |
| 523 |
{ text: "Custom integrations", included: true, icon: "dashicons-yes-alt" }, |
| 524 |
{ text: "White label exports", included: false, icon: "dashicons-no-alt" }, |
| 525 |
{ text: "Dedicated manager", included: false, icon: "dashicons-no-alt" } |
| 526 |
]}, |
| 527 |
buttonText: { type: "string", default: "Get Started" }, |
| 528 |
buttonUrl: { type: "string", default: "#" }, |
| 529 |
buttonTarget: { type: "boolean", default: false }, |
| 530 |
isPopular: { type: "boolean", default: true }, |
| 531 |
popularLabel: { type: "string", default: "Most Popular" }, |
| 532 |
cardStyle: { type: "string", default: "shadow" }, |
| 533 |
layout: { type: "string", default: "vertical" }, |
| 534 |
iconStyle: { type: "string", default: "dashicons" }, |
| 535 |
showDescription: { type: "boolean", default: true }, |
| 536 |
showPriceSubtext: { type: "boolean", default: true }, |
| 537 |
showFeatureDivider: { type: "boolean", default: true }, |
| 538 |
featureListStyle: { type: "string", default: "icon" }, |
| 539 |
accentColor: { type: "string", default: "#2563eb" }, |
| 540 |
accentColorSecondary: { type: "string", default: "#1e40af" }, |
| 541 |
cardBg: { type: "string", default: "#ffffff" }, |
| 542 |
cardBorderColor: { type: "string", default: "#e2e8f0" }, |
| 543 |
cardBorderWidth: { type: "number", default: 2 }, |
| 544 |
cardBorderRadius: { type: "number", default: 16 }, |
| 545 |
cardPaddingTop: { type: "number", default: 36 }, |
| 546 |
cardPaddingRight: { type: "number", default: 32 }, |
| 547 |
cardPaddingBottom: { type: "number", default: 36 }, |
| 548 |
cardPaddingLeft: { type: "number", default: 32 }, |
| 549 |
planNameSize: { type: "number", default: 20 }, |
| 550 |
planNameWeight: { type: "string", default: "700" }, |
| 551 |
planNameColor: { type: "string", default: "#0f172a" }, |
| 552 |
descriptionSize: { type: "number", default: 14 }, |
| 553 |
descriptionColor: { type: "string", default: "#64748b" }, |
| 554 |
priceSize: { type: "number", default: 52 }, |
| 555 |
priceColor: { type: "string", default: "#0f172a" }, |
| 556 |
currencySize: { type: "number", default: 24 }, |
| 557 |
periodSize: { type: "number", default: 16 }, |
| 558 |
periodColor: { type: "string", default: "#64748b" }, |
| 559 |
priceSubtextSize: { type: "number", default: 13 }, |
| 560 |
priceSubtextColor: { type: "string", default: "#94a3b8" }, |
| 561 |
featureSize: { type: "number", default: 15 }, |
| 562 |
featureColor: { type: "string", default: "#334155" }, |
| 563 |
featureExcludedColor: { type: "string", default: "#94a3b8" }, |
| 564 |
featureIncludedIconColor: { type: "string", default: "#22c55e" }, |
| 565 |
featureExcludedIconColor: { type: "string", default: "#cbd5e1" }, |
| 566 |
featureGap: { type: "number", default: 12 }, |
| 567 |
btnBg: { type: "string", default: "#2563eb" }, |
| 568 |
btnColor: { type: "string", default: "#ffffff" }, |
| 569 |
btnBgHover: { type: "string", default: "#1d4ed8" }, |
| 570 |
btnColorHover: { type: "string", default: "#ffffff" }, |
| 571 |
btnBorderColor: { type: "string", default: "#2563eb" }, |
| 572 |
btnBorderWidth: { type: "number", default: 2 }, |
| 573 |
btnBorderRadius: { type: "number", default: 8 }, |
| 574 |
btnFontSize: { type: "number", default: 16 }, |
| 575 |
btnFontWeight: { type: "string", default: "600" }, |
| 576 |
btnPaddingV: { type: "number", default: 14 }, |
| 577 |
btnWidth: { type: "string", default: "full" }, |
| 578 |
badgeBg: { type: "string", default: "#2563eb" }, |
| 579 |
badgeColor: { type: "string", default: "#ffffff" }, |
| 580 |
badgeSize: { type: "number", default: 12 }, |
| 581 |
badgeBorderRadius: { type: "number", default: 50 }, |
| 582 |
headerBg: { type: "string", default: "transparent" }, |
| 583 |
headerStyle: { type: "string", default: "default" }, |
| 584 |
sectionGap: { type: "number", default: 24 } |
| 585 |
}, |
| 586 |
save: function (props) { |
| 587 |
var a = props.attributes; |
| 588 |
function oldBuildWrapStyle(a) { |
| 589 |
var shadow = a.cardStyle === 'shadow' ? '0 8px 32px rgba(0,0,0,0.10)' : |
| 590 |
a.cardStyle === 'shadow-lg' ? '0 20px 60px rgba(0,0,0,0.16)' : |
| 591 |
a.cardStyle === 'flat' ? 'none' : 'none'; |
| 592 |
return { |
| 593 |
'--bkbg-pt-accent': a.accentColor, |
| 594 |
'--bkbg-pt-accent2': a.accentColorSecondary, |
| 595 |
'--bkbg-pt-card-bg': a.cardBg, |
| 596 |
'--bkbg-pt-card-border-color': a.cardBorderColor, |
| 597 |
'--bkbg-pt-card-border-w': a.cardBorderWidth + 'px', |
| 598 |
'--bkbg-pt-card-radius': a.cardBorderRadius + 'px', |
| 599 |
'--bkbg-pt-card-pad': a.cardPaddingTop + 'px ' + a.cardPaddingRight + 'px ' + a.cardPaddingBottom + 'px ' + a.cardPaddingLeft + 'px', |
| 600 |
'--bkbg-pt-card-shadow': shadow, |
| 601 |
'--bkbg-pt-plan-name-size': a.planNameSize + 'px', |
| 602 |
'--bkbg-pt-plan-name-weight': a.planNameWeight, |
| 603 |
'--bkbg-pt-plan-name-color': a.planNameColor, |
| 604 |
'--bkbg-pt-desc-size': a.descriptionSize + 'px', |
| 605 |
'--bkbg-pt-desc-color': a.descriptionColor, |
| 606 |
'--bkbg-pt-price-size': a.priceSize + 'px', |
| 607 |
'--bkbg-pt-price-color': a.priceColor, |
| 608 |
'--bkbg-pt-currency-size': a.currencySize + 'px', |
| 609 |
'--bkbg-pt-period-size': a.periodSize + 'px', |
| 610 |
'--bkbg-pt-period-color': a.periodColor, |
| 611 |
'--bkbg-pt-subtext-size': a.priceSubtextSize + 'px', |
| 612 |
'--bkbg-pt-subtext-color': a.priceSubtextColor, |
| 613 |
'--bkbg-pt-feature-size': a.featureSize + 'px', |
| 614 |
'--bkbg-pt-feature-color': a.featureColor, |
| 615 |
'--bkbg-pt-feature-excl-color': a.featureExcludedColor, |
| 616 |
'--bkbg-pt-feature-incl-icon-color': a.featureIncludedIconColor, |
| 617 |
'--bkbg-pt-feature-excl-icon-color': a.featureExcludedIconColor, |
| 618 |
'--bkbg-pt-feature-gap': a.featureGap + 'px', |
| 619 |
'--bkbg-pt-btn-bg': a.btnBg, |
| 620 |
'--bkbg-pt-btn-color': a.btnColor, |
| 621 |
'--bkbg-pt-btn-bg-hover': a.btnBgHover, |
| 622 |
'--bkbg-pt-btn-color-hover': a.btnColorHover, |
| 623 |
'--bkbg-pt-btn-border-color': a.btnBorderColor, |
| 624 |
'--bkbg-pt-btn-border-w': a.btnBorderWidth + 'px', |
| 625 |
'--bkbg-pt-btn-radius': a.btnBorderRadius + 'px', |
| 626 |
'--bkbg-pt-btn-font-size': a.btnFontSize + 'px', |
| 627 |
'--bkbg-pt-btn-font-weight': a.btnFontWeight, |
| 628 |
'--bkbg-pt-btn-pad-v': a.btnPaddingV + 'px', |
| 629 |
'--bkbg-pt-badge-bg': a.badgeBg, |
| 630 |
'--bkbg-pt-badge-color': a.badgeColor, |
| 631 |
'--bkbg-pt-badge-size': a.badgeSize + 'px', |
| 632 |
'--bkbg-pt-badge-radius': a.badgeBorderRadius + 'px', |
| 633 |
'--bkbg-pt-header-bg': a.headerBg, |
| 634 |
'--bkbg-pt-section-gap': a.sectionGap + 'px' |
| 635 |
}; |
| 636 |
} |
| 637 |
var cardClass = 'bkbg-pt-card bkbg-pt-style-' + a.cardStyle + (a.isPopular ? ' is-popular' : '') + ' bkbg-pt-header-' + a.headerStyle; |
| 638 |
function saveFeatureIcon(feat) { |
| 639 |
if (a.featureListStyle === 'none') return null; |
| 640 |
if (a.featureListStyle === 'bullet') return el('span', { className: 'bkbg-pt-feat-bullet', 'aria-hidden': 'true' }); |
| 641 |
if (a.featureListStyle === 'check') return el('span', { className: 'bkbg-pt-feat-check' + (feat.included ? ' is-included' : ' is-excluded'), 'aria-hidden': 'true' }); |
| 642 |
var icon = feat.included ? 'dashicons-yes-alt' : 'dashicons-no-alt'; |
| 643 |
return el('span', { className: 'dashicons ' + icon + ' bkbg-pt-feat-icon' + (feat.included ? ' is-included' : ' is-excluded'), 'aria-hidden': 'true' }); |
| 644 |
} |
| 645 |
var currencyEl = el('span', { className: 'bkbg-pt-currency' }, a.currency); |
| 646 |
var priceEl = el('span', { className: 'bkbg-pt-price-amount' }, a.price); |
| 647 |
var periodEl = el('span', { className: 'bkbg-pt-period' }, a.pricePeriod); |
| 648 |
return el('div', wp.blockEditor.useBlockProps.save({ className: cardClass, style: oldBuildWrapStyle(a) }), |
| 649 |
el('div', { className: 'bkbg-pt-header' }, |
| 650 |
a.isPopular && el('div', { className: 'bkbg-pt-badge' }, a.popularLabel), |
| 651 |
el(RichText.Content, { tagName: 'div', className: 'bkbg-pt-plan-name', value: a.planName }), |
| 652 |
a.showDescription && el(RichText.Content, { tagName: 'div', className: 'bkbg-pt-description', value: a.planDescription }) |
| 653 |
), |
| 654 |
el('div', { className: 'bkbg-pt-pricing' }, |
| 655 |
el('div', { className: 'bkbg-pt-price-row' }, |
| 656 |
el('div', { className: 'bkbg-pt-price-main' }, |
| 657 |
a.currencyPosition === 'before' ? el(Fragment, {}, currencyEl, priceEl) : el(Fragment, {}, priceEl, currencyEl), |
| 658 |
periodEl |
| 659 |
), |
| 660 |
a.showPriceSubtext && a.priceSubtext && el('div', { className: 'bkbg-pt-subtext' }, a.priceSubtext) |
| 661 |
) |
| 662 |
), |
| 663 |
el('ul', { className: 'bkbg-pt-features bkbg-pt-feat-style-' + a.featureListStyle }, |
| 664 |
a.features.map(function (feat, i) { |
| 665 |
return el('li', { key: i, className: 'bkbg-pt-feat-item' + (feat.included ? ' is-included' : ' is-excluded') }, |
| 666 |
saveFeatureIcon(feat), |
| 667 |
el('span', { className: 'bkbg-pt-feat-text' }, feat.text) |
| 668 |
); |
| 669 |
}) |
| 670 |
), |
| 671 |
a.showFeatureDivider && el('hr', { className: 'bkbg-pt-divider' }), |
| 672 |
el('div', { className: 'bkbg-pt-cta' }, |
| 673 |
el('a', { |
| 674 |
href: a.buttonUrl || '#', |
| 675 |
className: 'bkbg-pt-btn bkbg-pt-btn-' + a.btnWidth, |
| 676 |
target: a.buttonTarget ? '_blank' : undefined, |
| 677 |
rel: a.buttonTarget ? 'noopener noreferrer' : undefined |
| 678 |
}, a.buttonText) |
| 679 |
) |
| 680 |
); |
| 681 |
}, |
| 682 |
migrate: function (attrs) { |
| 683 |
var n = Object.assign({}, attrs); |
| 684 |
n.nameTypo = { sizeDesktop: attrs.planNameSize || 20, weight: attrs.planNameWeight || '700' }; |
| 685 |
n.descTypo = { sizeDesktop: attrs.descriptionSize || 14 }; |
| 686 |
n.priceTypo = { sizeDesktop: attrs.priceSize || 52 }; |
| 687 |
n.featureTypo = { sizeDesktop: attrs.featureSize || 15 }; |
| 688 |
n.btnTypo = { sizeDesktop: attrs.btnFontSize || 16, weight: attrs.btnFontWeight || '600' }; |
| 689 |
delete n.planNameSize; delete n.planNameWeight; delete n.descriptionSize; |
| 690 |
delete n.priceSize; delete n.featureSize; delete n.btnFontSize; delete n.btnFontWeight; |
| 691 |
return n; |
| 692 |
} |
| 693 |
}] |
| 694 |
}); |
| 695 |
}() ); |
| 696 |
|