@o2vend/theme-cli 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of @o2vend/theme-cli might be problematic. Click here for more details.
- package/README.md +211 -0
- package/bin/o2vend +34 -0
- package/config/widget-map.json +45 -0
- package/package.json +64 -0
- package/src/commands/check.js +201 -0
- package/src/commands/generate.js +33 -0
- package/src/commands/init.js +302 -0
- package/src/commands/optimize.js +216 -0
- package/src/commands/package.js +208 -0
- package/src/commands/serve.js +105 -0
- package/src/commands/validate.js +191 -0
- package/src/lib/api-client.js +339 -0
- package/src/lib/dev-server.js +482 -0
- package/src/lib/file-watcher.js +80 -0
- package/src/lib/hot-reload.js +106 -0
- package/src/lib/liquid-engine.js +169 -0
- package/src/lib/liquid-filters.js +589 -0
- package/src/lib/mock-api-server.js +225 -0
- package/src/lib/mock-data.js +290 -0
- package/src/lib/widget-service.js +293 -0
|
@@ -0,0 +1,589 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* O2VEND Liquid Filters
|
|
3
|
+
* Standalone implementation - copied and adapted from webstore solution
|
|
4
|
+
* Update manually when webstore solution changes
|
|
5
|
+
*
|
|
6
|
+
* Source: o2vend-webstore/services/liquid-helper-service.js
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const moment = require('moment');
|
|
10
|
+
const _ = require('lodash');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Liquid Helper Service
|
|
14
|
+
* Provides additional filters and functions for liquid templates
|
|
15
|
+
*/
|
|
16
|
+
class LiquidHelperService {
|
|
17
|
+
constructor() {
|
|
18
|
+
this.filters = {
|
|
19
|
+
// Date filters
|
|
20
|
+
date: this.dateFilter.bind(this),
|
|
21
|
+
time_ago: this.timeAgoFilter.bind(this),
|
|
22
|
+
time_until: this.timeUntilFilter.bind(this),
|
|
23
|
+
|
|
24
|
+
// String filters
|
|
25
|
+
truncate: this.truncateFilter.bind(this),
|
|
26
|
+
capitalize: this.capitalizeFilter.bind(this),
|
|
27
|
+
upcase: this.upcaseFilter.bind(this),
|
|
28
|
+
downcase: this.downcaseFilter.bind(this),
|
|
29
|
+
strip: this.stripFilter.bind(this),
|
|
30
|
+
lstrip: this.lstripFilter.bind(this),
|
|
31
|
+
rstrip: this.rstripFilter.bind(this),
|
|
32
|
+
split: this.splitFilter.bind(this),
|
|
33
|
+
join: this.joinFilter.bind(this),
|
|
34
|
+
replace: this.replaceFilter.bind(this),
|
|
35
|
+
remove: this.removeFilter.bind(this),
|
|
36
|
+
prepend: this.prependFilter.bind(this),
|
|
37
|
+
append: this.appendFilter.bind(this),
|
|
38
|
+
|
|
39
|
+
// Number filters
|
|
40
|
+
plus: this.plusFilter.bind(this),
|
|
41
|
+
minus: this.minusFilter.bind(this),
|
|
42
|
+
times: this.timesFilter.bind(this),
|
|
43
|
+
divided_by: this.dividedByFilter.bind(this),
|
|
44
|
+
modulo: this.moduloFilter.bind(this),
|
|
45
|
+
round: this.roundFilter.bind(this),
|
|
46
|
+
ceil: this.ceilFilter.bind(this),
|
|
47
|
+
floor: this.floorFilter.bind(this),
|
|
48
|
+
abs: this.absFilter.bind(this),
|
|
49
|
+
|
|
50
|
+
// Array filters
|
|
51
|
+
first: this.firstFilter.bind(this),
|
|
52
|
+
last: this.lastFilter.bind(this),
|
|
53
|
+
sort: this.sortFilter.bind(this),
|
|
54
|
+
reverse: this.reverseFilter.bind(this),
|
|
55
|
+
uniq: this.uniqFilter.bind(this),
|
|
56
|
+
map: this.mapFilter.bind(this),
|
|
57
|
+
where: this.whereFilter.bind(this),
|
|
58
|
+
size: this.sizeFilter.bind(this),
|
|
59
|
+
slice: this.sliceFilter.bind(this),
|
|
60
|
+
|
|
61
|
+
// Object filters
|
|
62
|
+
default: this.defaultFilter.bind(this),
|
|
63
|
+
json: this.jsonFilter.bind(this),
|
|
64
|
+
|
|
65
|
+
// Money filters
|
|
66
|
+
money: this.moneyFilter.bind(this),
|
|
67
|
+
money_with_currency: this.moneyWithCurrencyFilter.bind(this),
|
|
68
|
+
money_with_settings: this.moneyWithSettingsFilter.bind(this),
|
|
69
|
+
|
|
70
|
+
// URL filters
|
|
71
|
+
url_encode: this.urlEncodeFilter.bind(this),
|
|
72
|
+
url_decode: this.urlDecodeFilter.bind(this),
|
|
73
|
+
|
|
74
|
+
// HTML filters
|
|
75
|
+
escape: this.escapeFilter.bind(this),
|
|
76
|
+
strip_html: this.stripHtmlFilter.bind(this),
|
|
77
|
+
strip_newlines: this.stripNewlinesFilter.bind(this),
|
|
78
|
+
|
|
79
|
+
// Image filters
|
|
80
|
+
img_url: this.imgUrlFilter.bind(this),
|
|
81
|
+
img_tag: this.imgTagFilter.bind(this),
|
|
82
|
+
|
|
83
|
+
// Product filters
|
|
84
|
+
product_url: this.productUrlFilter.bind(this),
|
|
85
|
+
collection_url: this.collectionUrlFilter.bind(this),
|
|
86
|
+
page_url: this.pageUrlFilter.bind(this),
|
|
87
|
+
|
|
88
|
+
// Utility filters
|
|
89
|
+
pluralize: this.pluralizeFilter.bind(this),
|
|
90
|
+
handleize: this.handleizeFilter.bind(this),
|
|
91
|
+
highlight: this.highlightFilter.bind(this),
|
|
92
|
+
truncatewords: this.truncateWordsFilter.bind(this),
|
|
93
|
+
newline_to_br: this.newlineToBrFilter.bind(this),
|
|
94
|
+
|
|
95
|
+
// Asset versioning filter (adds version query parameter)
|
|
96
|
+
asset_url: this.assetUrlFilter.bind(this),
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Get all available filters
|
|
102
|
+
* @returns {Object} Filters object
|
|
103
|
+
*/
|
|
104
|
+
getFilters() {
|
|
105
|
+
return this.filters;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Date filters
|
|
109
|
+
dateFilter(input, format) {
|
|
110
|
+
if (!input) return '';
|
|
111
|
+
|
|
112
|
+
let date;
|
|
113
|
+
if (typeof input === 'string') {
|
|
114
|
+
const normalizedInput = input.trim();
|
|
115
|
+
const lowerInput = normalizedInput.toLowerCase();
|
|
116
|
+
|
|
117
|
+
if (lowerInput === 'now' || lowerInput === 'today') {
|
|
118
|
+
date = moment();
|
|
119
|
+
} else if (normalizedInput.match(/^\d{4}-\d{2}-\d{2}t\d{2}:\d{2}:\d{2}/i)) {
|
|
120
|
+
date = moment(normalizedInput);
|
|
121
|
+
} else if (normalizedInput.match(/^\d{4}-\d{2}-\d{2}/)) {
|
|
122
|
+
date = moment(normalizedInput, 'YYYY-MM-DD');
|
|
123
|
+
} else {
|
|
124
|
+
if (normalizedInput.match(/[©a-zA-Z]/) && !normalizedInput.match(/^\d{4}/)) {
|
|
125
|
+
if (format && (format.includes('%Y') || format.includes('YYYY'))) {
|
|
126
|
+
return moment().format('YYYY');
|
|
127
|
+
}
|
|
128
|
+
return input;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
date = moment(normalizedInput, moment.ISO_8601, true);
|
|
132
|
+
if (!date.isValid()) {
|
|
133
|
+
try {
|
|
134
|
+
date = moment(normalizedInput);
|
|
135
|
+
} catch (e) {
|
|
136
|
+
if (format && (format.includes('%Y') || format.includes('YYYY'))) {
|
|
137
|
+
return moment().format('YYYY');
|
|
138
|
+
}
|
|
139
|
+
return input;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
} else if (input instanceof Date) {
|
|
144
|
+
date = moment(input);
|
|
145
|
+
} else if (typeof input === 'number') {
|
|
146
|
+
date = moment.unix(input);
|
|
147
|
+
} else {
|
|
148
|
+
try {
|
|
149
|
+
date = moment(input);
|
|
150
|
+
} catch (e) {
|
|
151
|
+
if (format && (format.includes('%Y') || format.includes('YYYY'))) {
|
|
152
|
+
return moment().format('YYYY');
|
|
153
|
+
}
|
|
154
|
+
return input;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (!date || !date.isValid()) {
|
|
159
|
+
if (format && (format.includes('%Y') || format.includes('YYYY'))) {
|
|
160
|
+
return moment().format('YYYY');
|
|
161
|
+
}
|
|
162
|
+
return input;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const momentFormat = (format || 'YYYY-MM-DD')
|
|
166
|
+
.replace(/%Y/g, 'YYYY')
|
|
167
|
+
.replace(/%m/g, 'MM')
|
|
168
|
+
.replace(/%d/g, 'DD')
|
|
169
|
+
.replace(/%H/g, 'HH')
|
|
170
|
+
.replace(/%M/g, 'mm')
|
|
171
|
+
.replace(/%S/g, 'ss')
|
|
172
|
+
.replace(/%y/g, 'YY');
|
|
173
|
+
|
|
174
|
+
return date.format(momentFormat);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
timeAgoFilter(input) {
|
|
178
|
+
if (!input) return '';
|
|
179
|
+
|
|
180
|
+
let date;
|
|
181
|
+
if (typeof input === 'string') {
|
|
182
|
+
const normalizedInput = input.trim().toLowerCase();
|
|
183
|
+
if (normalizedInput === 'now' || normalizedInput === 'today') {
|
|
184
|
+
date = moment();
|
|
185
|
+
} else if (normalizedInput.match(/^\d{4}-\d{2}-\d{2}t\d{2}:\d{2}:\d{2}/)) {
|
|
186
|
+
date = moment(normalizedInput);
|
|
187
|
+
} else if (normalizedInput.match(/^\d{4}-\d{2}-\d{2}/)) {
|
|
188
|
+
date = moment(normalizedInput, 'YYYY-MM-DD');
|
|
189
|
+
} else {
|
|
190
|
+
date = moment(normalizedInput, moment.ISO_8601, true);
|
|
191
|
+
if (!date.isValid()) {
|
|
192
|
+
date = moment(normalizedInput);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
} else if (input instanceof Date) {
|
|
196
|
+
date = moment(input);
|
|
197
|
+
} else {
|
|
198
|
+
date = moment(input);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return date.isValid() ? date.fromNow() : input;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
timeUntilFilter(input) {
|
|
205
|
+
if (!input) return '';
|
|
206
|
+
|
|
207
|
+
let date;
|
|
208
|
+
if (typeof input === 'string') {
|
|
209
|
+
const normalizedInput = input.trim().toLowerCase();
|
|
210
|
+
if (normalizedInput === 'now' || normalizedInput === 'today') {
|
|
211
|
+
date = moment();
|
|
212
|
+
} else if (normalizedInput.match(/^\d{4}-\d{2}-\d{2}t\d{2}:\d{2}:\d{2}/)) {
|
|
213
|
+
date = moment(normalizedInput);
|
|
214
|
+
} else if (normalizedInput.match(/^\d{4}-\d{2}-\d{2}/)) {
|
|
215
|
+
date = moment(normalizedInput, 'YYYY-MM-DD');
|
|
216
|
+
} else {
|
|
217
|
+
date = moment(normalizedInput, moment.ISO_8601, true);
|
|
218
|
+
if (!date.isValid()) {
|
|
219
|
+
date = moment(normalizedInput);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
} else if (input instanceof Date) {
|
|
223
|
+
date = moment(input);
|
|
224
|
+
} else {
|
|
225
|
+
date = moment(input);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return date.isValid() ? date.toNow() : input;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// String filters
|
|
232
|
+
truncateFilter(input, length = 50, truncateString = '...') {
|
|
233
|
+
if (!input || typeof input !== 'string') return input;
|
|
234
|
+
return input.length > length ?
|
|
235
|
+
input.substring(0, length) + truncateString :
|
|
236
|
+
input;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
capitalizeFilter(input) {
|
|
240
|
+
if (!input || typeof input !== 'string') return input;
|
|
241
|
+
return input.charAt(0).toUpperCase() + input.slice(1).toLowerCase();
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
upcaseFilter(input) {
|
|
245
|
+
return input && typeof input === 'string' ? input.toUpperCase() : input;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
downcaseFilter(input) {
|
|
249
|
+
return input && typeof input === 'string' ? input.toLowerCase() : input;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
stripFilter(input) {
|
|
253
|
+
return input && typeof input === 'string' ? input.trim() : input;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
lstripFilter(input) {
|
|
257
|
+
return input && typeof input === 'string' ? input.replace(/^\s+/, '') : input;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
rstripFilter(input) {
|
|
261
|
+
return input && typeof input === 'string' ? input.replace(/\s+$/, '') : input;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
splitFilter(input, delimiter) {
|
|
265
|
+
if (!input || typeof input !== 'string') return [];
|
|
266
|
+
return input.split(delimiter || ' ');
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
joinFilter(input, delimiter) {
|
|
270
|
+
if (!Array.isArray(input)) return input;
|
|
271
|
+
return input.join(delimiter || ' ');
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
replaceFilter(input, search, replace) {
|
|
275
|
+
if (!input || typeof input !== 'string') return input;
|
|
276
|
+
return input.replace(new RegExp(search, 'g'), replace);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
removeFilter(input, search) {
|
|
280
|
+
if (!input || typeof input !== 'string') return input;
|
|
281
|
+
return input.replace(new RegExp(search, 'g'), '');
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
prependFilter(input, prepend) {
|
|
285
|
+
if (!input) return prepend || '';
|
|
286
|
+
return (prepend || '') + input;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
appendFilter(input, append) {
|
|
290
|
+
if (!input) return append || '';
|
|
291
|
+
return input + (append || '');
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Number filters
|
|
295
|
+
plusFilter(input, number) {
|
|
296
|
+
const num = parseFloat(input);
|
|
297
|
+
const add = parseFloat(number);
|
|
298
|
+
return isNaN(num) || isNaN(add) ? input : num + add;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
minusFilter(input, number) {
|
|
302
|
+
const num = parseFloat(input);
|
|
303
|
+
const sub = parseFloat(number);
|
|
304
|
+
return isNaN(num) || isNaN(sub) ? input : num - sub;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
timesFilter(input, number) {
|
|
308
|
+
const num = parseFloat(input);
|
|
309
|
+
const mult = parseFloat(number);
|
|
310
|
+
return isNaN(num) || isNaN(mult) ? input : num * mult;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
dividedByFilter(input, number) {
|
|
314
|
+
const num = parseFloat(input);
|
|
315
|
+
const div = parseFloat(number);
|
|
316
|
+
return isNaN(num) || isNaN(div) || div === 0 ? input : num / div;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
moduloFilter(input, number) {
|
|
320
|
+
const num = parseFloat(input);
|
|
321
|
+
const mod = parseFloat(number);
|
|
322
|
+
return isNaN(num) || isNaN(mod) || mod === 0 ? input : num % mod;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
roundFilter(input, decimals = 0) {
|
|
326
|
+
const num = parseFloat(input);
|
|
327
|
+
return isNaN(num) ? input : Math.round(num * Math.pow(10, decimals)) / Math.pow(10, decimals);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
ceilFilter(input) {
|
|
331
|
+
const num = parseFloat(input);
|
|
332
|
+
return isNaN(num) ? input : Math.ceil(num);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
floorFilter(input) {
|
|
336
|
+
const num = parseFloat(input);
|
|
337
|
+
return isNaN(num) ? input : Math.floor(num);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
absFilter(input) {
|
|
341
|
+
const num = parseFloat(input);
|
|
342
|
+
return isNaN(num) ? input : Math.abs(num);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Array filters
|
|
346
|
+
firstFilter(input) {
|
|
347
|
+
return Array.isArray(input) && input.length > 0 ? input[0] : input;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
lastFilter(input) {
|
|
351
|
+
return Array.isArray(input) && input.length > 0 ? input[input.length - 1] : input;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
sortFilter(input, property) {
|
|
355
|
+
if (!Array.isArray(input)) return input;
|
|
356
|
+
if (property) {
|
|
357
|
+
return input.sort((a, b) => {
|
|
358
|
+
const aVal = _.get(a, property);
|
|
359
|
+
const bVal = _.get(b, property);
|
|
360
|
+
return aVal > bVal ? 1 : aVal < bVal ? -1 : 0;
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
return input.sort();
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
reverseFilter(input) {
|
|
367
|
+
return Array.isArray(input) ? input.slice().reverse() : input;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
uniqFilter(input, property) {
|
|
371
|
+
if (!Array.isArray(input)) return input;
|
|
372
|
+
if (property) {
|
|
373
|
+
const seen = new Set();
|
|
374
|
+
return input.filter(item => {
|
|
375
|
+
const val = _.get(item, property);
|
|
376
|
+
if (seen.has(val)) return false;
|
|
377
|
+
seen.add(val);
|
|
378
|
+
return true;
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
return [...new Set(input)];
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
mapFilter(input, property) {
|
|
385
|
+
if (!Array.isArray(input)) return input;
|
|
386
|
+
return input.map(item => _.get(item, property));
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
whereFilter(input, property, value) {
|
|
390
|
+
if (!Array.isArray(input)) return input;
|
|
391
|
+
return input.filter(item => _.get(item, property) === value);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
sizeFilter(input) {
|
|
395
|
+
if (Array.isArray(input)) return input.length;
|
|
396
|
+
if (typeof input === 'string') return input.length;
|
|
397
|
+
if (typeof input === 'object' && input !== null) return Object.keys(input).length;
|
|
398
|
+
return 0;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
sliceFilter(input, start, length) {
|
|
402
|
+
if (!Array.isArray(input)) return input;
|
|
403
|
+
return input.slice(start, start + length);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Object filters
|
|
407
|
+
defaultFilter(input, defaultValue) {
|
|
408
|
+
return input || defaultValue;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
jsonFilter(input) {
|
|
412
|
+
try {
|
|
413
|
+
return JSON.stringify(input);
|
|
414
|
+
} catch (error) {
|
|
415
|
+
return input;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// Money filters
|
|
420
|
+
moneyFilter(input, currency = 'USD') {
|
|
421
|
+
const num = parseFloat(input);
|
|
422
|
+
if (isNaN(num)) return input;
|
|
423
|
+
|
|
424
|
+
const formatter = new Intl.NumberFormat('en-US', {
|
|
425
|
+
style: 'currency',
|
|
426
|
+
currency: currency
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
return formatter.format(num);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
moneyWithCurrencyFilter(input, currency = 'USD') {
|
|
433
|
+
return this.moneyFilter(input, currency);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
moneyWithSettingsFilter(input, settings = {}) {
|
|
437
|
+
const num = parseFloat(input);
|
|
438
|
+
if (isNaN(num)) return input;
|
|
439
|
+
|
|
440
|
+
const {
|
|
441
|
+
currencySymbol = '₹',
|
|
442
|
+
currencyFormat = '#,##0.00',
|
|
443
|
+
currencyDecimalDigits = 2,
|
|
444
|
+
currencyGroupSeparator = ',',
|
|
445
|
+
currencyDecimalSeparator = '.',
|
|
446
|
+
currencyGroupSizes = [3]
|
|
447
|
+
} = settings;
|
|
448
|
+
|
|
449
|
+
const formatNumberWithSettings = (num, settings) => {
|
|
450
|
+
const {
|
|
451
|
+
decimalDigits = 2,
|
|
452
|
+
groupSeparator = ',',
|
|
453
|
+
decimalSeparator = '.',
|
|
454
|
+
groupSizes = [3]
|
|
455
|
+
} = settings;
|
|
456
|
+
|
|
457
|
+
let formatted = num.toFixed(decimalDigits);
|
|
458
|
+
const parts = formatted.split('.');
|
|
459
|
+
let integerPart = parts[0];
|
|
460
|
+
const decimalPart = parts[1];
|
|
461
|
+
|
|
462
|
+
if (groupSizes.length > 0) {
|
|
463
|
+
const groupSize = groupSizes[0];
|
|
464
|
+
const regex = new RegExp(`\\B(?=(\\d{${groupSize}})+(?!\\d))`, 'g');
|
|
465
|
+
integerPart = integerPart.replace(regex, groupSeparator);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
if (decimalPart && decimalDigits > 0) {
|
|
469
|
+
return `${integerPart}${decimalSeparator}${decimalPart}`;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
return integerPart;
|
|
473
|
+
};
|
|
474
|
+
|
|
475
|
+
const formatted = formatNumberWithSettings(num, {
|
|
476
|
+
decimalDigits: currencyDecimalDigits,
|
|
477
|
+
groupSeparator: currencyGroupSeparator,
|
|
478
|
+
decimalSeparator: currencyDecimalSeparator,
|
|
479
|
+
groupSizes: currencyGroupSizes
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
return `${currencySymbol}${formatted}`;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// URL filters
|
|
486
|
+
urlEncodeFilter(input) {
|
|
487
|
+
return input && typeof input === 'string' ? encodeURIComponent(input) : input;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
urlDecodeFilter(input) {
|
|
491
|
+
return input && typeof input === 'string' ? decodeURIComponent(input) : input;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// HTML filters
|
|
495
|
+
escapeFilter(input) {
|
|
496
|
+
if (!input || typeof input !== 'string') return input;
|
|
497
|
+
return input
|
|
498
|
+
.replace(/&/g, '&')
|
|
499
|
+
.replace(/</g, '<')
|
|
500
|
+
.replace(/>/g, '>')
|
|
501
|
+
.replace(/"/g, '"')
|
|
502
|
+
.replace(/'/g, ''');
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
stripHtmlFilter(input) {
|
|
506
|
+
if (!input || typeof input !== 'string') return input;
|
|
507
|
+
return input.replace(/<[^>]*>/g, '');
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
stripNewlinesFilter(input) {
|
|
511
|
+
if (!input || typeof input !== 'string') return input;
|
|
512
|
+
return input.replace(/\n/g, ' ').replace(/\r/g, '');
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// Image filters
|
|
516
|
+
imgUrlFilter(input, size = 'original') {
|
|
517
|
+
if (!input) return '';
|
|
518
|
+
// In a real implementation, this would generate proper image URLs
|
|
519
|
+
return input;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
imgTagFilter(input, alt = '', attributes = '') {
|
|
523
|
+
if (!input) return '';
|
|
524
|
+
return `<img src="${input}" alt="${alt}" ${attributes}>`;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// Product filters
|
|
528
|
+
productUrlFilter(product) {
|
|
529
|
+
if (!product) return '#';
|
|
530
|
+
return `/products/${product.slug || product.handle || product.id}`;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
collectionUrlFilter(collection) {
|
|
534
|
+
if (!collection) return '#';
|
|
535
|
+
return `/collections/${collection.slug || collection.handle || collection.id}`;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
pageUrlFilter(page) {
|
|
539
|
+
if (!page) return '#';
|
|
540
|
+
return `/pages/${page.slug || page.handle || page.id}`;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// Utility filters
|
|
544
|
+
pluralizeFilter(input, singular, plural) {
|
|
545
|
+
const count = parseInt(input);
|
|
546
|
+
if (isNaN(count)) return input;
|
|
547
|
+
return count === 1 ? singular : plural;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
handleizeFilter(input) {
|
|
551
|
+
if (!input || typeof input !== 'string') return input;
|
|
552
|
+
return input
|
|
553
|
+
.toLowerCase()
|
|
554
|
+
.replace(/[^a-z0-9\s-]/g, '')
|
|
555
|
+
.replace(/\s+/g, '-')
|
|
556
|
+
.replace(/-+/g, '-')
|
|
557
|
+
.replace(/^-|-$/g, '');
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
highlightFilter(input, search, className = 'highlight') {
|
|
561
|
+
if (!input || !search || typeof input !== 'string') return input;
|
|
562
|
+
const regex = new RegExp(`(${search})`, 'gi');
|
|
563
|
+
return input.replace(regex, `<span class="${className}">$1</span>`);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
truncateWordsFilter(input, words = 15, truncateString = '...') {
|
|
567
|
+
if (!input || typeof input !== 'string') return input;
|
|
568
|
+
const wordArray = input.split(' ');
|
|
569
|
+
if (wordArray.length <= words) return input;
|
|
570
|
+
return wordArray.slice(0, words).join(' ') + truncateString;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
newlineToBrFilter(input) {
|
|
574
|
+
if (!input || typeof input !== 'string') return input;
|
|
575
|
+
return input.replace(/\n/g, '<br>');
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
/**
|
|
579
|
+
* Asset URL filter - returns asset URL as-is
|
|
580
|
+
* @param {string} input - Asset URL path
|
|
581
|
+
* @returns {string} Asset URL (unchanged)
|
|
582
|
+
*/
|
|
583
|
+
assetUrlFilter(input) {
|
|
584
|
+
if (!input || typeof input !== 'string') return input;
|
|
585
|
+
return input;
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
module.exports = LiquidHelperService;
|