@isrd-isi-edu/ermrestjs 2.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.
- package/LICENSE +202 -0
- package/README.md +55 -0
- package/dist/ermrest.d.ts +3481 -0
- package/dist/ermrest.js +45 -0
- package/dist/ermrest.js.gz +0 -0
- package/dist/ermrest.js.map +1 -0
- package/dist/ermrest.min.js +45 -0
- package/dist/ermrest.min.js.gz +0 -0
- package/dist/ermrest.min.js.map +1 -0
- package/dist/ermrest.ver.txt +1 -0
- package/dist/stats.html +4949 -0
- package/js/ag_reference.js +1483 -0
- package/js/core.js +4931 -0
- package/js/datapath.js +336 -0
- package/js/export.js +956 -0
- package/js/filters.js +192 -0
- package/js/format.js +344 -0
- package/js/hatrac.js +1130 -0
- package/js/json_ld_validator.js +285 -0
- package/js/parser.js +2320 -0
- package/js/setup/node.js +27 -0
- package/js/utils/helpers.js +2300 -0
- package/js/utils/json_ld_schema.js +680 -0
- package/js/utils/pseudocolumn_helpers.js +2196 -0
- package/package.json +79 -0
- package/src/index.ts +204 -0
- package/src/models/comment.ts +14 -0
- package/src/models/deferred-promise.ts +16 -0
- package/src/models/display-name.ts +5 -0
- package/src/models/errors.ts +408 -0
- package/src/models/path-prefix-alias-mapping.ts +130 -0
- package/src/models/reference/bulk-create-foreign-key-object.ts +133 -0
- package/src/models/reference/citation.ts +98 -0
- package/src/models/reference/contextualize.ts +535 -0
- package/src/models/reference/google-dataset-metadata.ts +72 -0
- package/src/models/reference/index.ts +14 -0
- package/src/models/reference/page.ts +520 -0
- package/src/models/reference/reference-aggregate-fn.ts +37 -0
- package/src/models/reference/reference.ts +2813 -0
- package/src/models/reference/related-reference.ts +467 -0
- package/src/models/reference/tuple.ts +652 -0
- package/src/models/reference-column/asset-pseudo-column.ts +498 -0
- package/src/models/reference-column/column-aggregate.ts +313 -0
- package/src/models/reference-column/facet-column.ts +1380 -0
- package/src/models/reference-column/foreign-key-pseudo-column.ts +626 -0
- package/src/models/reference-column/inbound-foreign-key-pseudo-column.ts +131 -0
- package/src/models/reference-column/index.ts +13 -0
- package/src/models/reference-column/key-pseudo-column.ts +236 -0
- package/src/models/reference-column/pseudo-column.ts +850 -0
- package/src/models/reference-column/reference-column.ts +740 -0
- package/src/models/source-object-node.ts +156 -0
- package/src/models/source-object-wrapper.ts +694 -0
- package/src/models/table-source-definitions.ts +98 -0
- package/src/services/authn.ts +43 -0
- package/src/services/catalog.ts +37 -0
- package/src/services/config.ts +202 -0
- package/src/services/error.ts +247 -0
- package/src/services/handlebars.ts +607 -0
- package/src/services/history.ts +136 -0
- package/src/services/http.ts +536 -0
- package/src/services/logger.ts +70 -0
- package/src/services/mustache.ts +0 -0
- package/src/utils/column-utils.ts +308 -0
- package/src/utils/constants.ts +526 -0
- package/src/utils/markdown-utils.ts +855 -0
- package/src/utils/reference-utils.ts +1658 -0
- package/src/utils/template-utils.ts +0 -0
- package/src/utils/type-utils.ts +89 -0
- package/src/utils/value-utils.ts +127 -0
- package/tsconfig.json +30 -0
- package/vite.config.mts +104 -0
|
@@ -0,0 +1,607 @@
|
|
|
1
|
+
/* eslint-disable no-useless-escape */
|
|
2
|
+
import Handlebars from 'handlebars';
|
|
3
|
+
import moment from 'moment-timezone';
|
|
4
|
+
|
|
5
|
+
import $log from '@isrd-isi-edu/ermrestjs/src/services/logger';
|
|
6
|
+
|
|
7
|
+
import { _handlebarsHelpersList } from '@isrd-isi-edu/ermrestjs/src/utils/constants';
|
|
8
|
+
import HistoryService from '@isrd-isi-edu/ermrestjs/src/services/history';
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
_addErmrestVarsToTemplate,
|
|
12
|
+
_addTemplateVars,
|
|
13
|
+
_escapeMarkdownCharacters,
|
|
14
|
+
_formatUtils,
|
|
15
|
+
_getPath,
|
|
16
|
+
encodeFacet,
|
|
17
|
+
encodeFacetString,
|
|
18
|
+
} from '@isrd-isi-edu/ermrestjs/js/utils/helpers';
|
|
19
|
+
import AuthnService from '@isrd-isi-edu/ermrestjs/src/services/authn';
|
|
20
|
+
import { isObjectAndNotNull } from '@isrd-isi-edu/ermrestjs/src/utils/type-utils';
|
|
21
|
+
import { fixedEncodeURIComponent } from '@isrd-isi-edu/ermrestjs/src/utils/value-utils';
|
|
22
|
+
import printf from '@isrd-isi-edu/ermrestjs/js/format';
|
|
23
|
+
import { type Catalog } from '@isrd-isi-edu/ermrestjs/js/core';
|
|
24
|
+
|
|
25
|
+
export default class HandlebarsService {
|
|
26
|
+
private static _setupDone = false;
|
|
27
|
+
private static _handlebarsHelpersHash: Record<string, boolean> = {};
|
|
28
|
+
// Cache to store all the handlebar templates to reduce compute time
|
|
29
|
+
private static _handlebarsCompiledTemplates: Record<string, HandlebarsTemplateDelegate> = {};
|
|
30
|
+
|
|
31
|
+
static get handlebars() {
|
|
32
|
+
if (!HandlebarsService._setupDone) {
|
|
33
|
+
HandlebarsService._setupDone = true;
|
|
34
|
+
|
|
35
|
+
// inject the custom handlebars
|
|
36
|
+
HandlebarsService._injectCustomHandlebarHelpers();
|
|
37
|
+
|
|
38
|
+
// loop through handlebars defined list of helpers and check against the enum in ermrestJs
|
|
39
|
+
// if not in enum, set helper to false
|
|
40
|
+
// should help defend against new helpers being exposed without us being aware of it
|
|
41
|
+
Object.keys(Handlebars.helpers).forEach(function (key) {
|
|
42
|
+
HandlebarsService._handlebarsHelpersHash[key] = _handlebarsHelpersList.includes(key);
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return Handlebars;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* @function
|
|
51
|
+
* @public
|
|
52
|
+
* @param template The template string to transform
|
|
53
|
+
* @param keyValues The key-value pair of object to be used for template tags replacement.
|
|
54
|
+
* @param catalog The catalog object created by ermrestJS representing the current catalog from the url
|
|
55
|
+
* @param options Configuration options.
|
|
56
|
+
* @return {string} A string produced after templating
|
|
57
|
+
* @desc Calls the private function to return a string produced as a result of templating using `Handlebars`.
|
|
58
|
+
*/
|
|
59
|
+
static render(template: string, keyValues: Record<string, any>, catalog: Catalog | { id: string }, options: any): string | null {
|
|
60
|
+
options = options || {};
|
|
61
|
+
|
|
62
|
+
const obj = _addTemplateVars(keyValues, catalog, options);
|
|
63
|
+
let content, _compiledTemplate;
|
|
64
|
+
|
|
65
|
+
// If we should validate, validate the template and if returns false, return null.
|
|
66
|
+
if (!options.avoidValidation && !HandlebarsService.validate(template, obj, catalog)) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
// Read template from cache
|
|
72
|
+
_compiledTemplate = HandlebarsService._handlebarsCompiledTemplates[template];
|
|
73
|
+
|
|
74
|
+
// If template not found then add it to cache
|
|
75
|
+
if (!_compiledTemplate) {
|
|
76
|
+
const compileOptions = {
|
|
77
|
+
knownHelpersOnly: true,
|
|
78
|
+
knownHelpers: HandlebarsService._handlebarsHelpersHash,
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
HandlebarsService._handlebarsCompiledTemplates[template] = _compiledTemplate = HandlebarsService.handlebars.compile(template, compileOptions);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Generate content from the template
|
|
85
|
+
content = _compiledTemplate(obj);
|
|
86
|
+
} catch (e) {
|
|
87
|
+
$log.error(e);
|
|
88
|
+
content = null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return content;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Returns true if all the used keys have values.
|
|
96
|
+
*
|
|
97
|
+
* NOTE:
|
|
98
|
+
* This implementation is very limited and if conditional Handlebar statements
|
|
99
|
+
* of the form {{#if }}{{/if}} or {{^if VARNAME}}{{/if}} or {{#unless VARNAME}}{{/unless}} or {{^unless }}{{/unless}} found then it won't check
|
|
100
|
+
* for null values and will return true.s
|
|
101
|
+
*
|
|
102
|
+
* @param template mustache template
|
|
103
|
+
* @param keyValues key-value pairs
|
|
104
|
+
* @param catalog the catalog object
|
|
105
|
+
* @param ignoredColumns the columns that should be ignored (optional)
|
|
106
|
+
* @return true if all the used keys have values
|
|
107
|
+
*/
|
|
108
|
+
static validate(template: string, keyValues: Record<string, any>, catalog: any, ignoredColumns?: string[]): boolean {
|
|
109
|
+
const conditionalRegex = /\{\{(((#|\^)([^\{\}]+))|(if|unless|else))([^\{\}]+)\}\}/;
|
|
110
|
+
let i, key, value;
|
|
111
|
+
|
|
112
|
+
// Inject ermrest internal utility objects such as date
|
|
113
|
+
// needs to be done in the case _validateTemplate is called without first calling _renderTemplate
|
|
114
|
+
_addErmrestVarsToTemplate(keyValues, catalog);
|
|
115
|
+
|
|
116
|
+
// If no conditional handlebars statements of the form {{#if VARNAME}}{{/if}} or {{^if VARNAME}}{{/if}} or {{#unless VARNAME}}{{/unless}} or {{^unless VARNAME}}{{/unless}} not found then do direct null check
|
|
117
|
+
if (!conditionalRegex.exec(template)) {
|
|
118
|
+
// Grab all placeholders ({{PROP_NAME}}) in the template
|
|
119
|
+
const placeholders = template.match(/\{\{([^\{\}\(\)\s]+)\}\}/gi);
|
|
120
|
+
|
|
121
|
+
// These will match the placeholders that are encapsulated in square brackets {{[string with space]}} or {{{[string with space]}}}
|
|
122
|
+
const specialPlaceholders = template.match(/\{\{((\[[^\{\}]+\])|(\{\[[^\{\}]+\]\}))\}\}/gi);
|
|
123
|
+
|
|
124
|
+
// If there are any placeholders
|
|
125
|
+
if (placeholders && placeholders.length) {
|
|
126
|
+
// Get unique placeholders
|
|
127
|
+
const uniquePlaceholders = placeholders.filter(function (item, i, ar) {
|
|
128
|
+
return ar.indexOf(item) === i && item !== 'else';
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
/*
|
|
132
|
+
* Iterate over all placeholders to set pattern as null if any of the
|
|
133
|
+
* values turn out to be null or undefined
|
|
134
|
+
*/
|
|
135
|
+
for (i = 0; i < uniquePlaceholders.length; i++) {
|
|
136
|
+
// Grab actual key from the placeholder {{name}} = name, remove "{{" and "}}" from the string for key
|
|
137
|
+
key = uniquePlaceholders[i].substring(2, uniquePlaceholders[i].length - 2);
|
|
138
|
+
|
|
139
|
+
if (key[0] == '{') key = key.substring(1, key.length - 1);
|
|
140
|
+
|
|
141
|
+
// find the value.
|
|
142
|
+
value = _getPath(keyValues, key.trim());
|
|
143
|
+
|
|
144
|
+
// TODO since we're not going inside the object this logic of ignoredColumns is not needed anymore,
|
|
145
|
+
// it was a hack that was added for asset columns.
|
|
146
|
+
// If key is not in ingored columns value for the key is null or undefined then return null
|
|
147
|
+
if ((!Array.isArray(ignoredColumns) || ignoredColumns.indexOf(key) === -1) && (value === null || value === undefined)) {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// If there are any placeholders
|
|
154
|
+
if (specialPlaceholders && specialPlaceholders.length) {
|
|
155
|
+
// Get unique placeholders
|
|
156
|
+
const uniqueSpecialPlaceholders = specialPlaceholders.filter(function (item, i, ar) {
|
|
157
|
+
return ar.indexOf(item) === i && item !== 'else';
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
/*
|
|
161
|
+
* Iterate over all specialPlaceholders to set pattern as null if any of the
|
|
162
|
+
* values turn out to be null or undefined
|
|
163
|
+
*/
|
|
164
|
+
for (i = 0; i < uniqueSpecialPlaceholders.length; i++) {
|
|
165
|
+
// Grab actual key from the placeholder {{name}} = name, remove "{{" and "}}" from the string for key
|
|
166
|
+
key = uniqueSpecialPlaceholders[i].substring(2, uniqueSpecialPlaceholders[i].length - 2);
|
|
167
|
+
|
|
168
|
+
if (key[0] == '{') key = key.substring(1, key.length - 1);
|
|
169
|
+
|
|
170
|
+
// Remove [] from the key {{[name]}} = name, remove "[" and "]" from the string for key
|
|
171
|
+
key = key.substring(1, key.length - 1);
|
|
172
|
+
|
|
173
|
+
// find the value.
|
|
174
|
+
value = _getPath(keyValues, key.trim());
|
|
175
|
+
|
|
176
|
+
// TODO since we're not going inside the object this logic of ignoredColumns is not needed anymore,
|
|
177
|
+
// it was a hack that was added for asset columns.
|
|
178
|
+
// If key is not in ingored columns value for the key is null or undefined then return null
|
|
179
|
+
if ((!Array.isArray(ignoredColumns) || ignoredColumns.indexOf(key) === -1) && (value === null || value === undefined)) {
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
private static _injectCustomHandlebarHelpers() {
|
|
189
|
+
// general purpose helpers
|
|
190
|
+
Handlebars.registerHelper({
|
|
191
|
+
/**
|
|
192
|
+
* escape markdown characters
|
|
193
|
+
* @ignore
|
|
194
|
+
* @returns escaped characeters
|
|
195
|
+
*/
|
|
196
|
+
escape: function (...args) {
|
|
197
|
+
// last argument is options object provided by handlebars
|
|
198
|
+
const text = args.splice(0, args.length - 1).join('');
|
|
199
|
+
return _escapeMarkdownCharacters(text);
|
|
200
|
+
},
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* @ignore
|
|
204
|
+
* @returns url-encoded string
|
|
205
|
+
*/
|
|
206
|
+
encode: function (...args) {
|
|
207
|
+
const text = args.splice(0, args.length - 1).join('');
|
|
208
|
+
return fixedEncodeURIComponent(text);
|
|
209
|
+
},
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* {{#encodeFacet}}
|
|
213
|
+
* str
|
|
214
|
+
* {{/encodeFacet}}
|
|
215
|
+
*
|
|
216
|
+
* or
|
|
217
|
+
*
|
|
218
|
+
* {{encodeFacet obj}}
|
|
219
|
+
*
|
|
220
|
+
* or
|
|
221
|
+
*
|
|
222
|
+
* {{encodeFacet str}}
|
|
223
|
+
*
|
|
224
|
+
* This is order of checking syntax (first applicaple rule):
|
|
225
|
+
* - first see if the block syntax with str inside it is used or not
|
|
226
|
+
* - if the input is an object we will encode it
|
|
227
|
+
* - try encoding as string (will return empty string if it wasn't a string)
|
|
228
|
+
* @ignore
|
|
229
|
+
* @returns encoded facet string that can be used in url
|
|
230
|
+
*/
|
|
231
|
+
encodeFacet: function (options: Handlebars.HelperOptions | string | any) {
|
|
232
|
+
try {
|
|
233
|
+
return encodeFacetString(options.fn(this));
|
|
234
|
+
} catch {
|
|
235
|
+
if (isObjectAndNotNull(options)) {
|
|
236
|
+
return encodeFacet(options);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return encodeFacetString(options);
|
|
240
|
+
},
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* {{printf value "%4d" }}
|
|
244
|
+
* @ignore
|
|
245
|
+
*/
|
|
246
|
+
printf: function (value, format) {
|
|
247
|
+
return printf({ format: format }, value);
|
|
248
|
+
},
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* {{formatDatetime value format}}
|
|
252
|
+
* @ignore
|
|
253
|
+
* @returns formatted string of `value` with corresponding `format`
|
|
254
|
+
*/
|
|
255
|
+
formatDatetime: function (value, format) {
|
|
256
|
+
const m = moment(value);
|
|
257
|
+
// if we don't validate, it will return "invalid date"
|
|
258
|
+
if (m.isValid()) {
|
|
259
|
+
return m.format(format);
|
|
260
|
+
}
|
|
261
|
+
return '';
|
|
262
|
+
},
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* {{formatDate value format}}
|
|
266
|
+
* @deprecated use formatDatetime instead
|
|
267
|
+
* @returns formatted string of `value` with corresponding `format`
|
|
268
|
+
*/
|
|
269
|
+
formatDate: function (value, format) {
|
|
270
|
+
const m = moment(value);
|
|
271
|
+
// if we don't validate, it will return "invalid date"
|
|
272
|
+
if (m.isValid()) {
|
|
273
|
+
return m.format(format);
|
|
274
|
+
}
|
|
275
|
+
return '';
|
|
276
|
+
},
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* {{#jsonStringify}}
|
|
280
|
+
* JSON Object
|
|
281
|
+
* {{/jsonStringify}}
|
|
282
|
+
*
|
|
283
|
+
* or
|
|
284
|
+
*
|
|
285
|
+
* {{#jsonStringify obj}}{{/jsonStringify}}
|
|
286
|
+
* @ignore
|
|
287
|
+
* @returns string representation of the given JSON object
|
|
288
|
+
*/
|
|
289
|
+
jsonStringify: function (options) {
|
|
290
|
+
try {
|
|
291
|
+
return JSON.stringify(options.fn(this));
|
|
292
|
+
} catch {
|
|
293
|
+
return JSON.stringify(options);
|
|
294
|
+
}
|
|
295
|
+
},
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* {{#replace substr newSubstr}}
|
|
299
|
+
* string
|
|
300
|
+
* {{/replace}}
|
|
301
|
+
*
|
|
302
|
+
* {{replace value regexp flags="ig"}}
|
|
303
|
+
* @ignore
|
|
304
|
+
* @returns replaces each match of the regexp with newSubstr
|
|
305
|
+
*/
|
|
306
|
+
replace: function (substr: string, newSubstr: string, options) {
|
|
307
|
+
let flags = 'g';
|
|
308
|
+
if (options && isObjectAndNotNull(options.hash) && typeof options.hash.flags === 'string') {
|
|
309
|
+
flags = options.hash.flags;
|
|
310
|
+
}
|
|
311
|
+
const regexpObj = new RegExp(substr, flags);
|
|
312
|
+
return options.fn(this).replace(regexpObj, newSubstr);
|
|
313
|
+
},
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* {{#if (regexMatch value regexp)}}
|
|
317
|
+
* .. content
|
|
318
|
+
* {{/if}}
|
|
319
|
+
*
|
|
320
|
+
* {{regexMatch value regexp flags="i"}}
|
|
321
|
+
* @ignore
|
|
322
|
+
* @returns boolean if the value matches the regexp
|
|
323
|
+
*/
|
|
324
|
+
regexMatch: function (value, regexp, options) {
|
|
325
|
+
let flags = 'g';
|
|
326
|
+
if (options && isObjectAndNotNull(options.hash) && typeof options.hash.flags === 'string') {
|
|
327
|
+
flags = options.hash.flags;
|
|
328
|
+
}
|
|
329
|
+
const regexpObj = new RegExp(regexp, flags);
|
|
330
|
+
return regexpObj.test(value);
|
|
331
|
+
},
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* {{#each (regexFindFirst value regexp)}}
|
|
335
|
+
* {{this}}
|
|
336
|
+
* {{/each}}
|
|
337
|
+
*
|
|
338
|
+
* {{regexFindFirst value regexp flags="i"}}
|
|
339
|
+
* @ignore
|
|
340
|
+
* @returns first string from value that matches the regular expression or empty string
|
|
341
|
+
*/
|
|
342
|
+
regexFindFirst: function (value, regexp, options) {
|
|
343
|
+
let flags = 'g';
|
|
344
|
+
if (options && isObjectAndNotNull(options.hash) && typeof options.hash.flags === 'string') {
|
|
345
|
+
flags = options.hash.flags;
|
|
346
|
+
}
|
|
347
|
+
const matches = regexpFindAll(value, regexp, flags);
|
|
348
|
+
return (matches && matches[0]) || '';
|
|
349
|
+
},
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* {{#each (regexFindAll value regexp)}}
|
|
353
|
+
* {{this}}
|
|
354
|
+
* {{/each}}
|
|
355
|
+
*
|
|
356
|
+
* {{regexFindFirst value regexp flags="ig"}}
|
|
357
|
+
* @ignore
|
|
358
|
+
* @returns array of strings from value that match the regular expression or
|
|
359
|
+
*/
|
|
360
|
+
regexFindAll: function (value: string, regexp: string, options?: Handlebars.HelperOptions) {
|
|
361
|
+
let flags = 'g';
|
|
362
|
+
if (options && isObjectAndNotNull(options.hash) && typeof options.hash.flags === 'string') {
|
|
363
|
+
flags = options.hash.flags;
|
|
364
|
+
}
|
|
365
|
+
return regexpFindAll(value, regexp, flags) || [];
|
|
366
|
+
},
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* {{#toTitleCase}}
|
|
370
|
+
* string
|
|
371
|
+
* {{/toTitleCase}}
|
|
372
|
+
* @ignore
|
|
373
|
+
* @returns string representation of the given JSON object
|
|
374
|
+
*/
|
|
375
|
+
toTitleCase: function (options: Handlebars.HelperOptions) {
|
|
376
|
+
const str = options.fn(this);
|
|
377
|
+
// \w matches any word character
|
|
378
|
+
// \S matches any non-whitespace character
|
|
379
|
+
return str.replace(/\w\S*/g, function (txt) {
|
|
380
|
+
return txt.charAt(0).toUpperCase() + txt.substr(1);
|
|
381
|
+
});
|
|
382
|
+
},
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* {{humanizeBytes value }}
|
|
386
|
+
* {{humanizeBytes value mode='si' }}
|
|
387
|
+
* {{humanizeBytes value precision=4}}
|
|
388
|
+
* {{humanizeBytes value tooltip=true }}
|
|
389
|
+
* @ignore
|
|
390
|
+
* @returns formatted string of `value` with corresponding `mode`
|
|
391
|
+
*/
|
|
392
|
+
humanizeBytes: function (value: number, options: Handlebars.HelperOptions) {
|
|
393
|
+
let mode, precision, tooltip;
|
|
394
|
+
if (options && isObjectAndNotNull(options.hash)) {
|
|
395
|
+
mode = options.hash.mode;
|
|
396
|
+
precision = options.hash.precision;
|
|
397
|
+
tooltip = options.hash.tooltip;
|
|
398
|
+
}
|
|
399
|
+
return _formatUtils.humanizeBytes(value, mode, precision, tooltip);
|
|
400
|
+
},
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* {{stringLength value }}
|
|
404
|
+
* @ignore
|
|
405
|
+
* @returns the length of the given string
|
|
406
|
+
*/
|
|
407
|
+
stringLength: function (value: string) {
|
|
408
|
+
return value.length;
|
|
409
|
+
},
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* {{isUserInAcl group1 group2 }}}
|
|
413
|
+
* {{isUserInAcl groupArray }}
|
|
414
|
+
* {{isUserInAcl "https:/some-group" "https://another-group" }}
|
|
415
|
+
*
|
|
416
|
+
* @returns a boolean indicating if the user is in any of the given groups
|
|
417
|
+
*/
|
|
418
|
+
isUserInAcl: function (...args) {
|
|
419
|
+
const groups = args.reduce((acc, arg) => {
|
|
420
|
+
if (Array.isArray(arg)) {
|
|
421
|
+
return acc.concat(arg);
|
|
422
|
+
} else if (typeof arg === 'string') {
|
|
423
|
+
return acc.concat([arg]);
|
|
424
|
+
}
|
|
425
|
+
return acc;
|
|
426
|
+
}, []);
|
|
427
|
+
|
|
428
|
+
return AuthnService.isUserInAcl(groups);
|
|
429
|
+
},
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* {{dateTimeToSnapshot value}}
|
|
433
|
+
*/
|
|
434
|
+
datetimeToSnapshot: function (value: string) {
|
|
435
|
+
const m = moment(value);
|
|
436
|
+
// if we don't validate, it will return "invalid date"
|
|
437
|
+
if (m.isValid()) {
|
|
438
|
+
try {
|
|
439
|
+
return HistoryService.datetimeISOToSnapshot(m.toISOString());
|
|
440
|
+
} catch (e) {
|
|
441
|
+
$log.error(`error while converting ${value} to snapshot`);
|
|
442
|
+
$log.error(e);
|
|
443
|
+
return '';
|
|
444
|
+
}
|
|
445
|
+
} else {
|
|
446
|
+
$log.error(`invalid timestamp passed to datetimeToSnapshot: ${value}`);
|
|
447
|
+
}
|
|
448
|
+
return '';
|
|
449
|
+
},
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* {{snapshotToDatetime value}}
|
|
453
|
+
* {{snapshotToDatetime value 'YYYY-MM-DD HH:mm:ss'}}
|
|
454
|
+
*/
|
|
455
|
+
snapshotToDatetime: function (value: string, format?: string) {
|
|
456
|
+
try {
|
|
457
|
+
const iso = HistoryService.snapshotToDatetimeISO(value);
|
|
458
|
+
if (typeof format === 'string') {
|
|
459
|
+
return moment(iso).format(format);
|
|
460
|
+
}
|
|
461
|
+
return iso;
|
|
462
|
+
} catch (e) {
|
|
463
|
+
$log.error(`error in snapshotToDatetime while converting ${value} to ISO`);
|
|
464
|
+
$log.error(e);
|
|
465
|
+
return '';
|
|
466
|
+
}
|
|
467
|
+
},
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
// compare helpers
|
|
471
|
+
Handlebars.registerHelper({
|
|
472
|
+
/*
|
|
473
|
+
*{{#if (eq val1 val2)}}
|
|
474
|
+
* .. content
|
|
475
|
+
*{{/if}}
|
|
476
|
+
*/
|
|
477
|
+
eq: function (...args) {
|
|
478
|
+
return reduceOp(args, (a, b) => a === b);
|
|
479
|
+
},
|
|
480
|
+
/*
|
|
481
|
+
*{{#if (ne val1 val2)}}
|
|
482
|
+
* .. content
|
|
483
|
+
*{{/if}}
|
|
484
|
+
*/
|
|
485
|
+
ne: function (...args) {
|
|
486
|
+
return reduceOp(args, (a, b) => a !== b);
|
|
487
|
+
},
|
|
488
|
+
/*
|
|
489
|
+
*{{#if (lt val1 val2)}}
|
|
490
|
+
* .. content
|
|
491
|
+
*{{/if}}
|
|
492
|
+
*/
|
|
493
|
+
lt: function (...args) {
|
|
494
|
+
return reduceOp(args, (a, b) => a < b);
|
|
495
|
+
},
|
|
496
|
+
/*
|
|
497
|
+
*{{#if (gt val1 val2)}}
|
|
498
|
+
* .. content
|
|
499
|
+
*{{/if}}
|
|
500
|
+
*/
|
|
501
|
+
gt: function (...args) {
|
|
502
|
+
return reduceOp(args, (a, b) => a > b);
|
|
503
|
+
},
|
|
504
|
+
/*
|
|
505
|
+
*{{#if (lte val1 val2)}}
|
|
506
|
+
* .. content
|
|
507
|
+
*{{/if}}
|
|
508
|
+
*/
|
|
509
|
+
lte: function (...args) {
|
|
510
|
+
return reduceOp(args, (a, b) => a <= b);
|
|
511
|
+
},
|
|
512
|
+
/*
|
|
513
|
+
*{{#if (gte val1 val2)}}
|
|
514
|
+
* .. content
|
|
515
|
+
*{{/if}}
|
|
516
|
+
*/
|
|
517
|
+
gte: function (...args) {
|
|
518
|
+
return reduceOp(args, (a, b) => a >= b);
|
|
519
|
+
},
|
|
520
|
+
/*
|
|
521
|
+
*{{#if (and section1 section2)}}
|
|
522
|
+
* .. content
|
|
523
|
+
*{{/if}}
|
|
524
|
+
*/
|
|
525
|
+
and: function (...args) {
|
|
526
|
+
return reduceOp(args, (a, b) => a && b);
|
|
527
|
+
},
|
|
528
|
+
/*
|
|
529
|
+
*{{#if (or section1 section2)}}
|
|
530
|
+
* .. content
|
|
531
|
+
*{{/if}}
|
|
532
|
+
*/
|
|
533
|
+
or: function (...args) {
|
|
534
|
+
return reduceOp(args, (a: boolean, b: boolean) => a || b);
|
|
535
|
+
},
|
|
536
|
+
/*
|
|
537
|
+
*{{#if (not section1)}}
|
|
538
|
+
* .. content
|
|
539
|
+
*{{/if}}
|
|
540
|
+
*/
|
|
541
|
+
not: function (a) {
|
|
542
|
+
return !a;
|
|
543
|
+
},
|
|
544
|
+
/*
|
|
545
|
+
*{{#ifCond value "===" value2}}
|
|
546
|
+
* Values are equal!
|
|
547
|
+
*{{else}}
|
|
548
|
+
* Values are different!
|
|
549
|
+
*{{/ifCond}}
|
|
550
|
+
*/
|
|
551
|
+
ifCond: function (v1, operator: string, v2, options: Handlebars.HelperOptions) {
|
|
552
|
+
switch (operator) {
|
|
553
|
+
case '==':
|
|
554
|
+
// eslint-disable-next-line eqeqeq
|
|
555
|
+
return v1 == v2 ? options.fn(this) : options.inverse(this);
|
|
556
|
+
case '===':
|
|
557
|
+
return v1 === v2 ? options.fn(this) : options.inverse(this);
|
|
558
|
+
case '!=':
|
|
559
|
+
// eslint-disable-next-line eqeqeq
|
|
560
|
+
return v1 != v2 ? options.fn(this) : options.inverse(this);
|
|
561
|
+
case '!==':
|
|
562
|
+
return v1 !== v2 ? options.fn(this) : options.inverse(this);
|
|
563
|
+
case '<':
|
|
564
|
+
return v1 < v2 ? options.fn(this) : options.inverse(this);
|
|
565
|
+
case '<=':
|
|
566
|
+
return v1 <= v2 ? options.fn(this) : options.inverse(this);
|
|
567
|
+
case '>':
|
|
568
|
+
return v1 > v2 ? options.fn(this) : options.inverse(this);
|
|
569
|
+
case '>=':
|
|
570
|
+
return v1 >= v2 ? options.fn(this) : options.inverse(this);
|
|
571
|
+
case '&&':
|
|
572
|
+
return v1 && v2 ? options.fn(this) : options.inverse(this);
|
|
573
|
+
case '||':
|
|
574
|
+
return v1 || v2 ? options.fn(this) : options.inverse(this);
|
|
575
|
+
default:
|
|
576
|
+
return options.inverse(this);
|
|
577
|
+
}
|
|
578
|
+
},
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
// math helpers
|
|
582
|
+
Handlebars.registerHelper({
|
|
583
|
+
add: function (arg1: unknown, arg2: unknown) {
|
|
584
|
+
return Number(arg1) + Number(arg2);
|
|
585
|
+
},
|
|
586
|
+
|
|
587
|
+
subtract: function (arg1: unknown, arg2: unknown) {
|
|
588
|
+
return Number(arg1) - Number(arg2);
|
|
589
|
+
},
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// allows recursive support of the given reducer function to be applied to args
|
|
595
|
+
const reduceOp = function (args: any[], reducer: (a: boolean, b: boolean) => boolean) {
|
|
596
|
+
args = Array.from(args);
|
|
597
|
+
args.pop(); // => options
|
|
598
|
+
const first = args.shift();
|
|
599
|
+
return args.reduce(reducer, first);
|
|
600
|
+
};
|
|
601
|
+
|
|
602
|
+
const regexpFindAll = function (value: string, regexp: string, flags: string) {
|
|
603
|
+
const regexpObj = new RegExp(regexp, flags);
|
|
604
|
+
const matches = value.match(regexpObj);
|
|
605
|
+
|
|
606
|
+
return matches;
|
|
607
|
+
};
|