@teipublisher/pb-components 1.32.2 → 1.34.1

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/src/pb-view.js CHANGED
@@ -161,6 +161,14 @@ export class PbView extends pbMixin(LitElement) {
161
161
  url: {
162
162
  type: String
163
163
  },
164
+ /**
165
+ * If set, rewrite URLs to load pages as static HTML files,
166
+ * so no TEI Publisher instance is required. Use this in combination with
167
+ * [tei-publisher-static](https://github.com/eeditiones/tei-publisher-static).
168
+ */
169
+ static: {
170
+ type: Boolean
171
+ },
164
172
  /**
165
173
  * The server returns footnotes separately. Set this property
166
174
  * if you wish to append them to the main text.
@@ -328,6 +336,7 @@ export class PbView extends pbMixin(LitElement) {
328
336
  this._selector = new Map();
329
337
  this._chunks = [];
330
338
  this._scrollTarget = null;
339
+ this.static = false;
331
340
  }
332
341
 
333
342
  attributeChangedCallback(name, oldVal, newVal) {
@@ -584,20 +593,56 @@ export class PbView extends pbMixin(LitElement) {
584
593
 
585
594
  const loadContent = this.shadowRoot.getElementById('loadContent');
586
595
 
587
- if (!this.url) {
596
+ if (this.static) {
597
+ this._staticUrl(params).then((url) => {
598
+ loadContent.url = url;
599
+ loadContent.generateRequest();
600
+ });
601
+ } else {
602
+ if (!this.url) {
603
+ if (this.minApiVersion('1.0.0')) {
604
+ this.url = "api/parts";
605
+ } else {
606
+ this.url = "modules/lib/components.xql";
607
+ }
608
+ }
588
609
  if (this.minApiVersion('1.0.0')) {
589
- this.url = "api/parts";
610
+ loadContent.url = `${this.getEndpoint()}/${this.url}/${encodeURIComponent(this.getDocument().path)}/json`;
590
611
  } else {
591
- this.url = "modules/lib/components.xql";
612
+ loadContent.url = `${this.getEndpoint()}/${this.url}`;
592
613
  }
614
+ loadContent.params = params;
615
+ loadContent.generateRequest();
593
616
  }
594
- if (this.minApiVersion('1.0.0')) {
595
- loadContent.url = `${this.getEndpoint()}/${this.url}/${encodeURIComponent(this.getDocument().path)}/json`;
596
- } else {
597
- loadContent.url = `${this.getEndpoint()}/${this.url}`;
617
+ }
618
+
619
+ /**
620
+ * Use a static URL to load pre-generated content.
621
+ */
622
+ async _staticUrl(params) {
623
+ function createKey(paramNames) {
624
+ const urlComponents = [];
625
+ paramNames.sort().forEach(key => {
626
+ if (params[key]) {
627
+ urlComponents.push(`${key}=${params[key]}`);
628
+ }
629
+ });
630
+ return urlComponents.join('&');
598
631
  }
599
- loadContent.params = params;
600
- loadContent.generateRequest();
632
+
633
+ const index = await fetch(`index.json`)
634
+ .then((response) => response.json());
635
+ const paramNames = ['odd', 'view', 'xpath'];
636
+ this.querySelectorAll('pb-param').forEach((param) => paramNames.push(`user.${param.getAttribute('name')}`));
637
+ let url = createKey([...paramNames, 'root']);
638
+ let file = index[url];
639
+ if (!file) {
640
+ url = createKey(paramNames);
641
+ file = index[url];
642
+ }
643
+
644
+ console.log('<pb-view> Static lookup %s: %s', url, file);
645
+ return `${file}`;
601
646
  }
602
647
 
603
648
  _clear() {
@@ -838,7 +883,11 @@ export class PbView extends pbMixin(LitElement) {
838
883
  let link = document.createElement('link');
839
884
  link.setAttribute('rel', 'stylesheet');
840
885
  link.setAttribute('type', 'text/css');
841
- link.setAttribute('href', `${this.getEndpoint()}/transform/${this.getOdd()}.css`);
886
+ if (this.static) {
887
+ link.setAttribute('href', `/css/${this.getOdd()}.css`);
888
+ } else {
889
+ link.setAttribute('href', `${this.getEndpoint()}/transform/${this.getOdd()}.css`);
890
+ }
842
891
  links.push(link);
843
892
 
844
893
  if (this.loadCss) {
@@ -0,0 +1,521 @@
1
+ import { get as i18n } from "./pb-i18n.js";
2
+
3
+ export class SearchResultService {
4
+ /*
5
+ * SEARCH RESULT OBJECT
6
+ * Service that loads initial data from a datasource,
7
+ * can be a database or an API, and converts it in
8
+ * a format that can be used by the pb-timeline component.
9
+ *
10
+ * public methods:
11
+ * getMinDateStr()
12
+ * getMaxDateStr()
13
+ * getMinDate()
14
+ * getMaxDate()
15
+ * export()
16
+ * getIntervalSizes()
17
+ */
18
+
19
+ /*
20
+ * CONSRTUCTOR INPUTS EXPLAINED
21
+ * jsonData: data to load, object with
22
+ * keys => valid datestrings formatted YYYY-MM-DD
23
+ * values => number of results for this day
24
+ * maxInterval: max amount of bins allowed
25
+ * scopes: array of all 6 possible values for scope
26
+ */
27
+ constructor(jsonData = {}, maxInterval = 60, scopes = ["D", "W", "M", "Y", "5Y", "10Y"]) {
28
+ this.data = { invalid: {}, valid: {} };
29
+ this.maxInterval = maxInterval;
30
+ this.scopes = scopes;
31
+ this._validateJsonData(jsonData);
32
+ }
33
+
34
+ /*
35
+ * based on the loaded jsonData, compute
36
+ * - min date as dateStr or utc-date-object
37
+ * - max date as dateStr or utc-date-object
38
+ */
39
+ getMinDateStr() {
40
+ return Object.keys(this.data.valid).sort()[0];
41
+ }
42
+ getMaxDateStr() {
43
+ let days = Object.keys(this.data.valid);
44
+ return days.sort()[days.length - 1];
45
+ }
46
+ getMinDate() {
47
+ return this._dateStrToUTCDate(this.getMinDateStr());
48
+ }
49
+ getMaxDate() {
50
+ return this._dateStrToUTCDate(this.getMaxDateStr());
51
+ }
52
+
53
+ getEndOfRangeDate(scope, date) {
54
+ return this._UTCDateToDateStr(this._increaseDateBy(scope, date));
55
+ }
56
+
57
+ /*
58
+ * exports data for each scope
59
+ * when no argument is provided, the optimal scope based
60
+ * on the maxInterval (default 60) will be assigned
61
+ */
62
+ export(scope) {
63
+ // auto assign scope when no argument provided
64
+ scope = scope || this._autoAssignScope();
65
+ // validate scope
66
+ if (!this.scopes.includes(scope)) {
67
+ throw new Error(`invalid scope provided, expected: ["10Y", "5Y", "Y", "M", "W", "D"]. Got: "${scope}"`);
68
+ }
69
+ // initialize object to export
70
+ const exportData = {
71
+ data: [],
72
+ scope: scope,
73
+ binTitleRotated: this._binTitleRotatedLookup(scope)
74
+ }
75
+ if (Object.keys(this.data.valid).length === 0) {
76
+ return exportData;
77
+ }
78
+ // get start and end date
79
+ const startCategory = this._classify(this.getMinDateStr(), scope);
80
+ const startDateStr = this._getFirstDay(startCategory);
81
+ let currentDate = this._dateStrToUTCDate(startDateStr);
82
+ const endDate = this.getMaxDate();
83
+ // iterate until end of intervall reached, add binObject for each step
84
+ while (currentDate <= endDate) {
85
+ const currentDateStr = this._UTCDateToDateStr(currentDate);
86
+ const currentCategory = this._classify(currentDateStr, scope);
87
+ exportData.data.push(this._buildBinObject(currentDateStr, currentCategory, scope));
88
+ currentDate = this._increaseDateBy(scope, currentDate);
89
+ }
90
+ // count all values
91
+ Object.keys(this.data.valid).sort().forEach(dateStr => {
92
+ const currentCategory = this._classify(dateStr, scope);
93
+ const targetBinObject = exportData.data.find(it => it.category === currentCategory);
94
+ try {
95
+ const value = this.data.valid[dateStr];
96
+ if (typeof value === 'object') {
97
+ targetBinObject.value += value.count || 0;
98
+ if (value.info) {
99
+ targetBinObject.info = targetBinObject.info.concat(value.info);
100
+ }
101
+ } else {
102
+ targetBinObject.value += this.data.valid[dateStr] || 0;
103
+ }
104
+ } catch(e) {
105
+ console.log(e);
106
+ console.log("currentCategory");
107
+ console.log(currentCategory);
108
+ }
109
+ });
110
+ if (this.data.invalid) {
111
+ let invalid = 0;
112
+ let info = [];
113
+ Object.values(this.data.invalid).forEach((value) => {
114
+ if (typeof value === 'object') {
115
+ invalid += value.count || 0;
116
+ info = info.concat(value.info);
117
+ } else {
118
+ invalid += value;
119
+ }
120
+ });
121
+ if (invalid > 0) {
122
+ exportData.data.push({
123
+ tooltip: i18n('timeline.unknown'),
124
+ title: i18n('timeline.unknown'),
125
+ // binTitle: i18n('timeline.unknown'),
126
+ category: '?',
127
+ separator: true,
128
+ value: invalid,
129
+ info
130
+ });
131
+ }
132
+ }
133
+ return exportData;
134
+ }
135
+
136
+ /*
137
+ * returns optimal scope based on the maxInterval
138
+ * by computing the scope that meets the criteria
139
+ * nbr of bins <= maxInterval
140
+ */
141
+ _autoAssignScope() {
142
+ for (let i = 0; i < this.scopes.length; i++) {
143
+ if (this._computeIntervalSize(this.scopes[i]) <= this.maxInterval) {
144
+ return this.scopes[i];
145
+ }
146
+ }
147
+ throw new Error(`Interval too big! Computed: ${this._computeIntervalSize(this.scopes[i])}. Allowed: ${this.maxInterval}. Try to increase maxInterval.`);
148
+ }
149
+
150
+ /*
151
+ * splits input data in 2 sections
152
+ * => valid data
153
+ * => invalid (if not a vaid date, for example 2012-00-00 is invalid)
154
+ */
155
+ _validateJsonData(jsonData) {
156
+ Object.keys(jsonData).sort().forEach(key => {
157
+ if (this._isValidDateStr(key)) {
158
+ this.data.valid[key] = jsonData[key];
159
+ } else {
160
+ this.data.invalid[key] = jsonData[key];
161
+ }
162
+ });
163
+ }
164
+
165
+ /*
166
+ * lookup table which bin titles should be rotated
167
+ */
168
+ _binTitleRotatedLookup(scope) {
169
+ const lookup = {
170
+ "10Y": true,
171
+ "5Y": true,
172
+ "Y": true,
173
+ "M": false, // only exception not to rotate in monthly scope
174
+ "W": true,
175
+ "D": true,
176
+ }
177
+ return lookup[scope];
178
+ }
179
+
180
+ /*
181
+ * Helper method that builds a binObject that
182
+ * can be read by the pb-timeline component
183
+ */
184
+ _buildBinObject(dateStr, category, scope) {
185
+ const split = dateStr.split("-");
186
+ const yearStr = split[0];
187
+ const monthStr = split[1];
188
+ const dayStr = split[2];
189
+ // for all scopes this remains the same
190
+ const binObject = {
191
+ dateStr: dateStr,
192
+ category: category,
193
+ value: 0,
194
+ info: []
195
+ }
196
+ // scope specific bin data
197
+ if (scope === "10Y") {
198
+ binObject.tooltip = `${category} - ${Number(category) + 9}`; // 1900 - 1999
199
+ binObject.selectionStart = `${category}`;
200
+ binObject.selectionEnd = `${Number(category) + 9}`;
201
+ // seperator every 100 years (10 bins)
202
+ if (Number(category) % 100 === 0) {
203
+ binObject.title = `${category} - ${Number(category) + 99}`;
204
+ binObject.binTitle = category;
205
+ binObject.seperator = true;
206
+ };
207
+ } else if (scope === "5Y") {
208
+ binObject.tooltip = `${category} - ${Number(category) + 4}`; // 1995 - 1999
209
+ binObject.selectionStart = `${category}`;
210
+ binObject.selectionEnd = `${Number(category) + 4}`;
211
+ // seperator every 50 years (10 bins)
212
+ if (Number(category) % 50 === 0) {
213
+ binObject.title = `${category} - ${Number(category) + 49}`;
214
+ binObject.binTitle = category;
215
+ binObject.seperator = true;
216
+ }
217
+ } else if (scope === "Y") {
218
+ binObject.tooltip = category;
219
+ binObject.selectionStart = category;
220
+ binObject.selectionEnd = category;
221
+ // seperator every 10 years (10 bins)
222
+ if (Number(category) % 10 === 0) {
223
+ binObject.title = `${category} - ${Number(category) + 9}`;
224
+ binObject.binTitle = `${category}`;
225
+ binObject.seperator = true;
226
+ }
227
+ } else if (scope === "M") {
228
+ const monthNum = Number(monthStr);
229
+ const month = this._monthLookup(monthNum); // Jan,Feb,Mar,...,Nov,Dez
230
+ binObject.binTitle = month[0]; // J,F,M,A,M,J,J,..N,D
231
+ binObject.tooltip = `${month} ${yearStr}`; // May 1996
232
+ binObject.selectionStart = `${month} ${yearStr}`;
233
+ binObject.selectionEnd = `${month} ${yearStr}`;
234
+ // every first of the month
235
+ if (monthNum === 1) {
236
+ binObject.title = yearStr; // YYYY
237
+ binObject.seperator = true;
238
+ }
239
+ } else if (scope === "W") {
240
+ const week = category.split("-")[1];; // => W52
241
+ binObject.tooltip = `${yearStr} ${week}`; // 1996 W52
242
+ binObject.selectionStart = `${yearStr} ${week}`; // 1996 W52
243
+ binObject.selectionEnd = `${yearStr} ${week}`; // 1996 W52
244
+ let currentDate = this._dateStrToUTCDate(dateStr);
245
+ let lastWeek = this._addDays(currentDate, -7);
246
+ // title and binTitle every first monday of the month
247
+ if (currentDate.getUTCMonth() !== lastWeek.getUTCMonth()) {
248
+ binObject.binTitle = week;
249
+ binObject.title = this._monthLookup(currentDate.getUTCMonth() + 1);
250
+ }
251
+ // seperator every start of the year
252
+ binObject.seperator = week === "W1";
253
+ } else if (scope === "D") {
254
+ binObject.tooltip = dateStr;
255
+ binObject.selectionStart = dateStr;
256
+ binObject.selectionEnd = dateStr;
257
+ // every monday
258
+ if (this._dateStrToUTCDate(dateStr).getUTCDay() === 1) {
259
+ binObject.binTitle = `${Number(dayStr)}.${Number(monthStr)}`;
260
+ binObject.title = `${this._classify(dateStr, "W").replace("-", " ")}`;
261
+ binObject.seperator = true;
262
+ }
263
+ } else {
264
+ throw new Error(`invalid scope provided, expected: ["10Y", "5Y", "Y", "M", "W", "D"]. Got: "${scope}"`);
265
+ }
266
+ return binObject;
267
+ }
268
+
269
+ /*
270
+ * ...classifies dateStr into category (based on scope)
271
+ * EXAMPLES:
272
+ * _classify("2016-01-12", "10Y") // => "2010"
273
+ * _classify("2016-01-12", "5Y") // => "2015"
274
+ * _classify("2016-01-12", "Y") // => "2016"
275
+ * _classify("2016-01-12", "M") // => "2010-01"
276
+ * _classify("2016-01-12", "W") // => "2016-W2"
277
+ * _classify("2016-01-12", "D") // => "2016-01-12"
278
+ */
279
+ _classify(dateStr, scope) { // returns category (as string)
280
+ if (!dateStr.match(/^\d{4}-\d{2}-\d{2}$/)) { // quick validate dateStr
281
+ throw new Error(`invalid dateStr format, expected "YYYY-MM-DD", got: "${dateStr}".`);
282
+ }
283
+ if (!dateStr || !scope) { // both inputs provided
284
+ throw new Error(`both inputs must be provided. Got dateStr=${dateStr}, scope=${scope}`);
285
+ }
286
+ switch (scope) {
287
+ case "10Y": case "5Y":
288
+ const intervalSize = Number(scope.replace("Y", ""));
289
+ const startYear = Math.floor(Number(dateStr.split("-")[0]) / intervalSize) * intervalSize;
290
+ return startYear.toString();
291
+ case "Y":
292
+ return dateStr.substr(0, 4);
293
+ case "M":
294
+ return dateStr.substr(0, 7);
295
+ case "W":
296
+ const UTCDate = this._dateStrToUTCDate(dateStr);
297
+ return this._UTCDateToWeekFormat(UTCDate);
298
+ case "D":
299
+ return dateStr;
300
+ }
301
+ }
302
+
303
+ /*
304
+ * ...gets first day as UTC Date, based on the category
305
+ * EXAMPLES:
306
+ * _getFirstDay("2010") // => 2010-01-01
307
+ * _getFirstDay("2010-12") // => 2010-12-01
308
+ * _getFirstDay("2010-W10") // => 2010-03-08
309
+ */
310
+ _getFirstDay(categoryStr) {
311
+ if (categoryStr.match(/^\d{4}-\d{2}-\d{2}$/)) { // YYYY-MM-DD => return same value
312
+ return categoryStr;
313
+ }
314
+ if (categoryStr.match(/^\d{4}-\d{2}$/)) { // YYYY-MM
315
+ return `${categoryStr}-01`; // add -01
316
+ }
317
+ if (categoryStr.match(/^\d{4}$/)) { // YYYY
318
+ return `${categoryStr}-01-01`; // add -01-01
319
+ }
320
+ if (categoryStr.match(/^\d{4}-W([1-9]|[1-4][0-9]|5[0-3])$/)) { // YYYY-W? // ? => [1-53]
321
+ // |YYYY-W |1-9 | 10-49 | 50-53 |
322
+ const split = categoryStr.split("-");
323
+ const year = Number(split[0]);
324
+ const weekNumber = Number(split[1].replace("W", ""));
325
+ return this._getDateStrOfISOWeek(year, weekNumber);
326
+ }
327
+ throw new Error("invalid categoryStr");
328
+ }
329
+
330
+ /*
331
+ * converts dateStr (YYYY-MM-DD) to a date object in UTC time
332
+ */
333
+ _dateStrToUTCDate(dateStr) {
334
+ if (!this._isValidDateStr(dateStr)) {
335
+ throw new Error(`invalid dateStr, expected "YYYY-MM-DD" with month[1-12] and day[1-31], got: "${dateStr}".`);
336
+ }
337
+ const split = dateStr.split("-");
338
+ const year = Number(split[0]);
339
+ const month = Number(split[1]);
340
+ const day = Number(split[2]);
341
+ return new Date(Date.UTC(year, month - 1, day));
342
+ }
343
+
344
+ /*
345
+ * converts a UTC date object to a dateStr (YYYY-MM-DD)
346
+ */
347
+ _UTCDateToDateStr(UTCDate) {
348
+ return UTCDate.toISOString().split("T")[0];
349
+ }
350
+
351
+ /*
352
+ * example:
353
+ * 1 Jan 2020 => 2020-W1
354
+ */
355
+ _UTCDateToWeekFormat(UTCDate) {
356
+ const year = this._getISOWeekYear(UTCDate);
357
+ const weekNbr = this._getISOWeek(UTCDate);
358
+ return `${year}-W${weekNbr}`;
359
+ }
360
+
361
+ /*
362
+ * returns the ISO week (_getISOWeek) or year (_getISOWeekYear)
363
+ * as number based on a UTC date.
364
+ */
365
+ _getISOWeek(UTCDate) { // https://weeknumber.net/how-to/javascript
366
+ let date = new Date(UTCDate.getTime());
367
+ date.setHours(0, 0, 0, 0);
368
+ // Thursday in current week decides the year.
369
+ date.setDate(date.getDate() + 3 - (date.getDay() + 6) % 7);
370
+ // January 4 is always in week 1.
371
+ let week1 = new Date(date.getFullYear(), 0, 4);
372
+ // Adjust to Thursday in week 1 and count number of weeks from date to week1.
373
+ return 1 + Math.round(((date.getTime() - week1.getTime()) / 86400000
374
+ - 3 + (week1.getDay() + 6) % 7) / 7);
375
+ }
376
+ /*
377
+ * returns the ISO week year as number based on a UTC date
378
+ * this is only needed for rollovers, for example:
379
+ * => 1.jan 2011 is in W52 of year 2010.
380
+ */
381
+ _getISOWeekYear(UTCDate) { // https://weeknumber.net/how-to/javascript
382
+ var date = new Date(UTCDate.getTime());
383
+ date.setDate(date.getDate() + 3 - (date.getDay() + 6) % 7);
384
+ return date.getFullYear();
385
+ }
386
+
387
+ /*
388
+ * given the year and weeknumber -> return dateStr (YYYY-MM-DD)
389
+ */
390
+ _getDateStrOfISOWeek(year, weekNumber) { // https://stackoverflow.com/a/16591175/6272061
391
+ let simple = new Date(Date.UTC(year, 0, 1 + (weekNumber - 1) * 7));
392
+ let dow = simple.getUTCDay();
393
+ let ISOweekStart = simple;
394
+ if (dow <= 4)
395
+ ISOweekStart.setDate(simple.getDate() - simple.getUTCDay() + 1);
396
+ else
397
+ ISOweekStart.setDate(simple.getDate() + 8 - simple.getUTCDay());
398
+ return ISOweekStart.toISOString().split("T")[0];
399
+ }
400
+
401
+ /*
402
+ * compute the interval sizes based on the scope
403
+ * prediction only, not the actuall export of the data
404
+ */
405
+ getIntervalSizes() {
406
+ return {
407
+ "D": this._computeIntervalSize("D"),
408
+ "W": this._computeIntervalSize("W"),
409
+ "M": this._computeIntervalSize("M"),
410
+ "Y": this._computeIntervalSize("Y"),
411
+ "5Y": this._computeIntervalSize("5Y"),
412
+ "10Y": this._computeIntervalSize("10Y"),
413
+ }
414
+ }
415
+ _computeIntervalSize(scope) {
416
+ const maxDate = this.getMaxDateStr();
417
+ if (!maxDate) {
418
+ return 0;
419
+ }
420
+ const endDate = this._dateStrToUTCDate(maxDate);
421
+ const firstDayDateStr = this._getFirstDay(this._classify(this.getMinDateStr(), scope));
422
+ let currentDate = this._dateStrToUTCDate(firstDayDateStr);
423
+ let count = 0;
424
+ while (currentDate <= endDate) {
425
+ count++;
426
+ currentDate = this._increaseDateBy(scope, currentDate);
427
+ }
428
+ return count;
429
+ }
430
+ _increaseDateBy(scope, date) {
431
+ switch (scope) {
432
+ case "D":
433
+ return this._addDays(date, 1);
434
+ case "W":
435
+ return this._addDays(date, 7);
436
+ case "M":
437
+ return this._addMonths(date, 1);
438
+ case "Y":
439
+ return this._addYears(date, 1);
440
+ case "5Y":
441
+ return this._addYears(date, 5);
442
+ case "10Y":
443
+ return this._addYears(date, 10);
444
+ }
445
+ }
446
+
447
+ /*
448
+ * functions that add n days (_addDays), months (_addMonths)
449
+ * or years (_addYears) to a UTC date object
450
+ * returns the computed new UTC date
451
+ */
452
+ _addDays(UTCDate, days) {
453
+ let newUTCDate = new Date(UTCDate.valueOf());
454
+ newUTCDate.setUTCDate(newUTCDate.getUTCDate() + days);
455
+ return newUTCDate;
456
+ }
457
+ _addMonths(UTCdate, months) {
458
+ let newUTCDate = new Date(UTCdate.valueOf());
459
+ let d = newUTCDate.getUTCDate();
460
+ newUTCDate.setUTCMonth(newUTCDate.getUTCMonth() + +months);
461
+ if (newUTCDate.getUTCDate() != d) {
462
+ newUTCDate.setUTCDate(0);
463
+ }
464
+ return newUTCDate;
465
+ }
466
+ _addYears(UTCdate, years) {
467
+ let newUTCDate = new Date(UTCdate.valueOf());
468
+ newUTCDate.setUTCFullYear(newUTCDate.getUTCFullYear() + years);
469
+ return newUTCDate;
470
+ }
471
+
472
+ /*
473
+ * Validates dateStr. rules:
474
+ * => year: 4 digit number
475
+ * => month: [1-12]
476
+ * => day: [1-31]
477
+ */
478
+ _isValidDateStr(str) {
479
+ if (!str) {
480
+ return false;
481
+ }
482
+
483
+ let split = str.split("-");
484
+ if (split.length !== 3) return false;
485
+ let year = split[0];
486
+ let month = split[1];
487
+ let day = split[2];
488
+ if (year === "0000" || day === "00" || month === "00") return false;
489
+ if (Number(day) < 1 || Number(day) > 31) return false;
490
+ if (Number(month) < 1 || Number(month) > 12) return false;
491
+ // if all checks are passed => valid datestring!
492
+ return true;
493
+ }
494
+
495
+ /*
496
+ * Converts month number (str or number) to a 3 char
497
+ * abbreviation of the month (in english)
498
+ */
499
+ _monthLookup(num) {
500
+ if (num > 12 || num < 1) {
501
+ throw new Error(`invalid 'num' provided, expected 1-12. Got: ${num}`);
502
+ }
503
+ const lookup = {
504
+ "1": "Jan",
505
+ "2": "Feb",
506
+ "3": "Mar",
507
+ "4": "Apr",
508
+ "5": "May",
509
+ "6": "Jun",
510
+ "7": "Jul",
511
+ "8": "Aug",
512
+ "9": "Sep",
513
+ "10": "Oct",
514
+ "11": "Nov",
515
+ "12": "Dec",
516
+ }
517
+ return lookup[num.toString()];
518
+ }
519
+ }
520
+
521
+