textbrowser 0.41.0 → 0.42.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.
@@ -1,4052 +0,0 @@
1
- #!/usr/bin/env node
2
- 'use strict';
3
-
4
- require('url-search-params-polyfill');
5
- var simpleGetJson = require('simple-get-json');
6
- var rtlDetect = require('rtl-detect');
7
- var jamilih = require('jamilih');
8
- var formSerialization = require('form-serialization');
9
- var imf = require('imf');
10
-
11
- function ownKeys(object, enumerableOnly) {
12
- var keys = Object.keys(object);
13
-
14
- if (Object.getOwnPropertySymbols) {
15
- var symbols = Object.getOwnPropertySymbols(object);
16
- enumerableOnly && (symbols = symbols.filter(function (sym) {
17
- return Object.getOwnPropertyDescriptor(object, sym).enumerable;
18
- })), keys.push.apply(keys, symbols);
19
- }
20
-
21
- return keys;
22
- }
23
-
24
- function _objectSpread2(target) {
25
- for (var i = 1; i < arguments.length; i++) {
26
- var source = null != arguments[i] ? arguments[i] : {};
27
- i % 2 ? ownKeys(Object(source), !0).forEach(function (key) {
28
- _defineProperty(target, key, source[key]);
29
- }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) {
30
- Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
31
- });
32
- }
33
-
34
- return target;
35
- }
36
-
37
- function _defineProperty(obj, key, value) {
38
- if (key in obj) {
39
- Object.defineProperty(obj, key, {
40
- value: value,
41
- enumerable: true,
42
- configurable: true,
43
- writable: true
44
- });
45
- } else {
46
- obj[key] = value;
47
- }
48
-
49
- return obj;
50
- }
51
-
52
- function _getRequireWildcardCache(nodeInterop) {
53
- if (typeof WeakMap !== "function") return null;
54
- var cacheBabelInterop = new WeakMap();
55
- var cacheNodeInterop = new WeakMap();
56
- return (_getRequireWildcardCache = function (nodeInterop) {
57
- return nodeInterop ? cacheNodeInterop : cacheBabelInterop;
58
- })(nodeInterop);
59
- }
60
-
61
- function _interopRequireWildcard(obj, nodeInterop) {
62
- if (!nodeInterop && obj && obj.__esModule) {
63
- return obj;
64
- }
65
-
66
- if (obj === null || typeof obj !== "object" && typeof obj !== "function") {
67
- return {
68
- default: obj
69
- };
70
- }
71
-
72
- var cache = _getRequireWildcardCache(nodeInterop);
73
-
74
- if (cache && cache.has(obj)) {
75
- return cache.get(obj);
76
- }
77
-
78
- var newObj = {};
79
- var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor;
80
-
81
- for (var key in obj) {
82
- if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) {
83
- var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null;
84
-
85
- if (desc && (desc.get || desc.set)) {
86
- Object.defineProperty(newObj, key, desc);
87
- } else {
88
- newObj[key] = obj[key];
89
- }
90
- }
91
- }
92
-
93
- newObj.default = obj;
94
-
95
- if (cache) {
96
- cache.set(obj, newObj);
97
- }
98
-
99
- return newObj;
100
- }
101
-
102
- function _objectWithoutPropertiesLoose(source, excluded) {
103
- if (source == null) return {};
104
- var target = {};
105
- var sourceKeys = Object.keys(source);
106
- var key, i;
107
-
108
- for (i = 0; i < sourceKeys.length; i++) {
109
- key = sourceKeys[i];
110
- if (excluded.indexOf(key) >= 0) continue;
111
- target[key] = source[key];
112
- }
113
-
114
- return target;
115
- }
116
-
117
- function _objectWithoutProperties(source, excluded) {
118
- if (source == null) return {};
119
-
120
- var target = _objectWithoutPropertiesLoose(source, excluded);
121
-
122
- var key, i;
123
-
124
- if (Object.getOwnPropertySymbols) {
125
- var sourceSymbolKeys = Object.getOwnPropertySymbols(source);
126
-
127
- for (i = 0; i < sourceSymbolKeys.length; i++) {
128
- key = sourceSymbolKeys[i];
129
- if (excluded.indexOf(key) >= 0) continue;
130
- if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue;
131
- target[key] = source[key];
132
- }
133
- }
134
-
135
- return target;
136
- }
137
-
138
- /**
139
- *
140
- * @param {string} param
141
- * @param {boolean} skip
142
- * @this IntlURLSearchParams
143
- * @returns {string}
144
- */
145
- function _prepareParam(param, skip) {
146
- if (skip || !this.localizeParamNames) {
147
- // (lang)
148
- return param;
149
- } // start, end, toggle
150
-
151
-
152
- const endNums = /\d+(-\d+)?$/; // eslint-disable-line unicorn/no-unsafe-regex
153
-
154
- const indexed = param.match(endNums);
155
-
156
- if (indexed) {
157
- // Todo: We could i18nize numbers as well
158
- return this.l10n(['params', 'indexed', param.replace(endNums, '')]) + indexed[0];
159
- }
160
-
161
- return this.l10n(['params', param]);
162
- }
163
-
164
- class IntlURLSearchParams {
165
- constructor({
166
- l10n,
167
- params
168
- } = {}) {
169
- this.l10n = l10n;
170
-
171
- if (!params) {
172
- params = location.hash.slice(1);
173
- }
174
-
175
- if (typeof params === 'string') {
176
- params = new URLSearchParams(params);
177
- }
178
-
179
- this.params = params;
180
- }
181
-
182
- get(param, skip) {
183
- return this.params.get(_prepareParam.call(this, param, skip));
184
- }
185
-
186
- getAll(param, skip) {
187
- return this.params.getAll(_prepareParam.call(this, param, skip));
188
- }
189
-
190
- has(param, skip) {
191
- return this.params.has(_prepareParam.call(this, param, skip));
192
- }
193
-
194
- delete(param, skip) {
195
- return this.params.delete(_prepareParam.call(this, param, skip));
196
- }
197
-
198
- set(param, value, skip) {
199
- return this.params.set(_prepareParam.call(this, param, skip), value);
200
- }
201
-
202
- append(param, value, skip) {
203
- return this.params.append(_prepareParam.call(this, param, skip), value);
204
- }
205
-
206
- toString() {
207
- return this.params.toString();
208
- }
209
-
210
- }
211
-
212
- /* eslint-env browser */
213
- var languageSelect = {
214
- main({
215
- langs,
216
- languages,
217
- followParams,
218
- $p
219
- }) {
220
- jamilih.jml('form', {
221
- class: 'focus',
222
- id: 'languageSelectionContainer',
223
- $on: {
224
- submit(e) {
225
- e.preventDefault();
226
- }
227
-
228
- }
229
- }, [['select', {
230
- name: 'lang',
231
- size: langs.length,
232
- $on: {
233
- click({
234
- target: {
235
- parentNode: {
236
- selectedOptions
237
- }
238
- }
239
- }) {
240
- followParams('#languageSelectionContainer', () => {
241
- $p.set('lang', selectedOptions[0].value, true);
242
- });
243
- }
244
-
245
- }
246
- }, langs.map(({
247
- code
248
- }) => ['option', {
249
- value: code
250
- }, [languages.getLanguageFromCode(code)]])]], jamilih.$('#main'));
251
-
252
- if (history.state && typeof history.state === 'object') {
253
- formSerialization.deserialize(document.querySelector('#languageSelectionContainer'), history.state);
254
- }
255
- } // Todo: Add in Go button (with 'submitgo' localization string) to
256
- // avoid need for pull-down if using first selection?
257
-
258
- /* Works too:
259
- langs.map(({code, name}) =>
260
- ['div', [
261
- ['a', {href: '#', dataset: {code}}, [name]]
262
- ]]
263
- ), $('#main')
264
- */
265
-
266
-
267
- };
268
-
269
- /* eslint-env browser */
270
- var workSelect = (({
271
- groups,
272
- lf,
273
- getNextAlias,
274
- $p,
275
- followParams
276
- }) => {
277
- const form = jamilih.jml('form', {
278
- id: 'workSelect',
279
- class: 'focus',
280
- $on: {
281
- submit(e) {
282
- e.preventDefault();
283
- }
284
-
285
- }
286
- }, groups.map((group, i) => ['div', [i > 0 ? ['br', 'br', 'br'] : '', ['div', [lf({
287
- key: group.directions.localeKey,
288
- fallback: true
289
- })]], ['br'], ['select', {
290
- class: 'file',
291
- name: 'work' + i,
292
- dataset: {
293
- name: group.name.localeKey
294
- },
295
- $on: {
296
- change({
297
- target: {
298
- value
299
- }
300
- }) {
301
- /*
302
- // If using click, but click doesn't always fire
303
- if (e.target.nodeName.toLowerCase() === 'select') {
304
- return;
305
- }
306
- */
307
- followParams('#workSelect', () => {
308
- $p.set('work', value);
309
- });
310
- }
311
-
312
- }
313
- }, [['option', {
314
- value: ''
315
- }, ['--']], ...group.files.map(({
316
- name: fileName
317
- }) => ['option', {
318
- value: lf(['workNames', group.id, fileName])
319
- }, [getNextAlias()]])]] // Todo: Add in Go button (with 'submitgo' localization string) to
320
- // avoid need for pull-down if using first selection?
321
- ]]), jamilih.$('#main'));
322
-
323
- if (history.state && typeof history.state === 'object') {
324
- formSerialization.deserialize(document.querySelector('#workSelect'), history.state);
325
- }
326
-
327
- return form;
328
- });
329
-
330
- const colors = ['aqua', 'black', 'blue', 'fuchsia', 'gray', 'green', 'lime', 'maroon', 'navy', 'olive', 'purple', 'red', 'silver', 'teal', 'white', 'yellow'];
331
- const fonts = ['Helvetica, sans-serif', 'Verdana, sans-serif', 'Gill Sans, sans-serif', 'Avantgarde, sans-serif', 'Helvetica Narrow, sans-serif', 'sans-serif', 'Times, serif', 'Times New Roman, serif', 'Palatino, serif', 'Bookman, serif', 'New Century Schoolbook, serif', 'serif', 'Andale Mono, monospace', 'Courier New, monospace', 'Courier, monospace', 'Lucidatypewriter, monospace', 'Fixed, monospace', 'monospace', 'Comic Sans, Comic Sans MS, cursive', 'Zapf Chancery, cursive', 'Coronetscript, cursive', 'Florence, cursive', 'Parkavenue, cursive', 'cursive', 'Impact, fantasy', 'Arnoldboecklin, fantasy', 'Oldtown, fantasy', 'Blippo, fantasy', 'Brushstroke, fantasy', 'fantasy'];
332
-
333
- const nbsp2 = jamilih.nbsp.repeat(2);
334
- const nbsp3 = jamilih.nbsp.repeat(3);
335
-
336
- const getDataForSerializingParamsAsURL = () => ({
337
- form: jamilih.$('form#browse'),
338
- // Todo: We don't need any default once random
339
- // functionality is completed
340
- random: jamilih.$('#rand') || {},
341
- checkboxes: jamilih.$$('input[type=checkbox]')
342
- });
343
-
344
- var workDisplay = {
345
- bdo: ({
346
- fallbackDirection,
347
- message
348
- }) => // Displaying as div with inline display instead of span since
349
- // Firefox puts punctuation at left otherwise (bdo dir
350
- // seemed to have issues in Firefox)
351
- ['div', {
352
- style: 'display: inline; direction: ' + fallbackDirection
353
- }, [message]],
354
- columnsTable: ({
355
- ld,
356
- fieldInfo,
357
- $p,
358
- le,
359
- iil,
360
- l,
361
- metadataObj,
362
- preferredLocale,
363
- schemaItems,
364
- fieldMatchesLocale
365
- }) => ['table', {
366
- border: '1',
367
- cellpadding: '5',
368
- align: 'center'
369
- }, [['tr', [['th', [ld('fieldno')]], ['th', {
370
- align: 'left',
371
- width: '20'
372
- }, [ld('field_enabled')]], ['th', [ld('field_title')]], ['th', [ld('fieldinterlin')]], ['th', [ld('fieldcss')]]
373
- /*
374
- Todo: Support search?
375
- ,
376
- ['th', [
377
- ld('fieldsearch')
378
- ]]
379
- */
380
- ]], ...fieldInfo.map((fieldInfoItem, i) => {
381
- const idx = i + 1;
382
- const checkedIndex = 'checked' + idx;
383
- const fieldIndex = 'field' + idx;
384
- const fieldParam = $p.get(fieldIndex);
385
- return ['tr', [// Todo: Get Jamilih to accept numbers and
386
- // booleans (`toString` is too dangerous)
387
- ['td', [String(idx)]], le('check-columns-to-browse', 'td', 'title', {}, [le('yes', 'input', 'value', {
388
- class: 'fieldSelector',
389
- id: checkedIndex,
390
- name: iil('checked') + idx,
391
- checked: $p.get(checkedIndex) !== l('no') && ($p.has(checkedIndex) || fieldInfoItem.onByDefault !== false),
392
- type: 'checkbox'
393
- })]), le('check-sequence', 'td', 'title', {}, [['select', {
394
- name: iil('field') + idx,
395
- id: fieldIndex,
396
- size: '1'
397
- }, fieldInfo.map(({
398
- field,
399
- fieldAliasOrName
400
- }, j) => {
401
- const matchedFieldParam = fieldParam && fieldParam === fieldAliasOrName;
402
- return ['option', {
403
- dataset: {
404
- name: field
405
- },
406
- value: fieldAliasOrName,
407
- selected: matchedFieldParam || j === i && !$p.has(fieldIndex)
408
- }, [fieldAliasOrName]];
409
- })]]), ['td', [// Todo: Make as tag selector with fields as options
410
- le('interlinear-tips', 'input', 'title', {
411
- name: iil('interlin') + idx,
412
- value: $p.get('interlin' + idx)
413
- }) // Todo: Could allow i18n of numbers here
414
- ]], ['td', [// Todo: Make as CodeMirror-highlighted CSS
415
- ['input', {
416
- name: iil('css') + idx,
417
- value: $p.get('css' + idx)
418
- }]]]
419
- /*
420
- ,
421
- ['td', [ // Todo: Allow plain or regexp searching
422
- ['input', {name: iil('search') + idx, value: $p.get('search' + idx)}]
423
- ]]
424
- */
425
- ]];
426
- }), ['tr', [['td', {
427
- colspan: 3
428
- }, [le('check_all', 'input', 'value', {
429
- type: 'button',
430
- $on: {
431
- click() {
432
- jamilih.$$('.fieldSelector').forEach(checkbox => {
433
- checkbox.checked = true;
434
- });
435
- }
436
-
437
- }
438
- }), le('uncheck_all', 'input', 'value', {
439
- type: 'button',
440
- $on: {
441
- click() {
442
- jamilih.$$('.fieldSelector').forEach(checkbox => {
443
- checkbox.checked = false;
444
- });
445
- }
446
-
447
- }
448
- }), le('checkmark_locale_fields_only', 'input', 'value', {
449
- type: 'button',
450
- $on: {
451
- click() {
452
- fieldInfo.forEach(({
453
- field
454
- }, i) => {
455
- const idx = i + 1; // The following is redundant with 'field' but may need to
456
- // retrieve later out of order?
457
-
458
- const fld = jamilih.$('#field' + idx).selectedOptions[0].dataset.name;
459
- jamilih.$('#checked' + idx).checked = fieldMatchesLocale(fld);
460
- });
461
- }
462
-
463
- }
464
- })]]]]]],
465
- advancedFormatting: ({
466
- ld,
467
- il,
468
- l,
469
- lo,
470
- le,
471
- $p,
472
- hideFormattingSection
473
- }) => ['td', {
474
- id: 'advancedformatting',
475
- style: {
476
- display: hideFormattingSection ? 'none' : 'block'
477
- }
478
- }, [['h3', [ld('advancedformatting')]], ['label', [ld('textcolor'), nbsp2, ['select', {
479
- name: il('colorName')
480
- }, colors.map((color, i) => {
481
- const atts = {
482
- value: l(['param_values', 'colors', color])
483
- };
484
-
485
- if ($p.get('colorName') === l(['param_values', 'colors', color]) || i === 1 && !$p.has('colorName')) {
486
- atts.selected = 'selected';
487
- }
488
-
489
- return lo(['param_values', 'colors', color], atts);
490
- })]]], ['label', [jamilih.nbsp, ld('or_entercolor'), nbsp2, ['input', {
491
- name: il('color'),
492
- type: 'text',
493
- value: $p.get('color') || '#',
494
- size: '7',
495
- maxlength: '7'
496
- }]]], ['br'], ['br'], ['label', [ld('backgroundcolor'), nbsp2, ['select', {
497
- name: il('bgcolorName')
498
- }, colors.map((color, i) => {
499
- const atts = {
500
- value: l(['param_values', 'colors', color])
501
- };
502
-
503
- if ($p.get('bgcolorName') === l(['param_values', 'colors', color]) || i === 14 && !$p.has('bgcolorName')) {
504
- atts.selected = 'selected';
505
- }
506
-
507
- return lo(['param_values', 'colors', color], atts);
508
- })]]], ['label', [jamilih.nbsp, ld('or_entercolor'), nbsp2, ['input', {
509
- name: il('bgcolor'),
510
- type: 'text',
511
- value: $p.get('bgcolor') || '#',
512
- size: '7',
513
- maxlength: '7'
514
- }]]], ['br'], ['br'], ['label', [ld('text_font'), nbsp2, // Todo: remove hard-coded direction if i81nizing; also i18nize fontSeq param
515
- ['select', {
516
- name: il('fontSeq'),
517
- dir: 'ltr'
518
- }, fonts.map((fontSeq, i) => {
519
- const atts = {
520
- value: fontSeq
521
- };
522
-
523
- if ($p.get('fontSeq') === fontSeq || i === 7 && !$p.has('fontSeq')) {
524
- atts.selected = 'selected';
525
- }
526
-
527
- return ['option', atts, [fontSeq]];
528
- })]]], ['br'], ['br'], ['label', [ld('font_style'), nbsp2, ['select', {
529
- name: il('fontstyle')
530
- }, ['italic', 'normal', 'oblique'].map((fontstyle, i) => {
531
- const atts = {
532
- value: l(['param_values', 'fontstyle', fontstyle])
533
- };
534
-
535
- if ($p.get('fontstyle') === l(['param_values', 'fontstyle', fontstyle]) || i === 1 && !$p.has('fontstyle')) {
536
- atts.selected = 'selected';
537
- }
538
-
539
- return lo(['param_values', 'fontstyle', fontstyle], atts);
540
- })]]], ['br'], ['div', [ld('font_variant'), nbsp3, ['label', [['input', {
541
- name: il('fontvariant'),
542
- type: 'radio',
543
- value: l(['param_values', 'fontvariant', 'normal']),
544
- checked: $p.get('fontvariant') !== ld(['param_values', 'fontvariant', 'small-caps'])
545
- }], ld(['param_values', 'fontvariant', 'normal']), jamilih.nbsp]], ['label', [['input', {
546
- name: il('fontvariant'),
547
- type: 'radio',
548
- value: l(['param_values', 'fontvariant', 'small-caps']),
549
- checked: $p.get('fontvariant') === ld(['param_values', 'fontvariant', 'small-caps'])
550
- }], ld(['param_values', 'fontvariant', 'small-caps']), jamilih.nbsp]]]], ['br'], ['label', [// Todo: i18n and allow for normal/bold pulldown and float input?
551
- ld('font_weight'), ' (normal, bold, 100-900, etc.):', nbsp2, ['input', {
552
- name: il('fontweight'),
553
- type: 'text',
554
- value: $p.has('fontweight') ? $p.get('fontweight') : 'normal',
555
- size: '7',
556
- maxlength: '12'
557
- }]]], ['br'], ['label', [ld('font_size'), ' (14pt, 14px, small, 75%, etc.):', nbsp2, ['input', {
558
- name: il('fontsize'),
559
- type: 'text',
560
- value: $p.get('fontsize'),
561
- size: '7',
562
- maxlength: '12'
563
- }]]], ['br'], // Todo: i18nize title and values?
564
- // Todo: remove hard-coded direction if i18nizing
565
- ['label', {
566
- dir: 'ltr'
567
- }, [ld('font_stretch'), jamilih.nbsp, ['select', {
568
- name: il('fontstretch')
569
- }, ['ultra-condensed', 'extra-condensed', 'condensed', 'semi-condensed', 'normal', 'semi-expanded', 'expanded', 'extra-expanded', 'ultra-expanded'].map(stretch => {
570
- const atts = {
571
- value: ld(['param_values', 'font-stretch', stretch])
572
- };
573
-
574
- if ($p.get('fontstretch') === stretch || !$p.has('fontstretch') && stretch === 'normal') {
575
- atts.selected = 'selected';
576
- }
577
-
578
- return ['option', atts, [ld(['param_values', 'font-stretch', stretch])]];
579
- })]]],
580
- /**/
581
- ['br'], ['br'], ['label', [ld('letter_spacing'), ' (normal, .9em, -.05cm): ', ['input', {
582
- name: il('letterspacing'),
583
- type: 'text',
584
- value: $p.has('letterspacing') ? $p.get('letterspacing') : 'normal',
585
- size: '7',
586
- maxlength: '12'
587
- }]]], ['br'], ['label', [ld('line_height'), ' (normal, 1.5, 22px, 150%): ', ['input', {
588
- name: il('lineheight'),
589
- type: 'text',
590
- value: $p.has('lineheight') ? $p.get('lineheight') : 'normal',
591
- size: '7',
592
- maxlength: '12'
593
- }]]], ['br'], ['br'], le('tableformatting_tips', 'h3', 'title', {}, [ld('tableformatting')]), ['div', [ld('header_wstyles'), nbsp2, ...[['yes', ld(['param_values', 'y'])], ['no', ld(['param_values', 'n'])], ['none', ld(['param_values', '0'])]].map(([key, val], i, arr) => ['label', [['input', {
594
- name: il('header'),
595
- type: 'radio',
596
- value: val,
597
- checked: $p.get('header') === val || !$p.has('header') && i === 1
598
- }], ld(key), i === arr.length - 1 ? '' : nbsp3]])]], ['div', [ld('footer_wstyles'), nbsp2, ...[['yes', ld(['param_values', 'y'])], ['no', ld(['param_values', 'n'])], ['none', ld(['param_values', '0'])]].map(([key, val], i, arr) => ['label', [['input', {
599
- name: il('footer'),
600
- type: 'radio',
601
- value: val,
602
- checked: $p.get('footer') === val || !$p.has('footer') && i === 2
603
- }], ld(key), i === arr.length - 1 ? '' : nbsp3]])]], ['label', [['input', {
604
- name: il('headerfooterfixed'),
605
- type: 'checkbox',
606
- value: l('yes'),
607
- checked: $p.get('headerfooterfixed') === l('yes')
608
- }], nbsp2, ld('headerfooterfixed-wishtoscroll')]], ['br'], ['div', [ld('caption_wstyles'), nbsp2, ...[['yes', ld(['param_values', 'y'])], ['no', ld(['param_values', 'n'])], ['none', ld(['param_values', '0'])]].map(([key, val], i, arr) => ['label', [['input', {
609
- name: il('caption'),
610
- type: 'radio',
611
- value: val,
612
- checked: $p.get('caption') === val || !$p.has('caption') && i === 2
613
- }], ld(key), i === arr.length - 1 ? '' : nbsp3]])]], ['br'], ['div', [ld('table_wborder'), nbsp2, ['label', [['input', {
614
- name: il('border'),
615
- type: 'radio',
616
- value: '1',
617
- checked: $p.get('border') !== '0'
618
- }], ld('yes'), nbsp3]], ['label', [['input', {
619
- name: il('border'),
620
- type: 'radio',
621
- value: '0',
622
- checked: $p.get('border') === '0'
623
- }], ld('no')]]]], ['div', [ld('interlin_repeat_field_names'), nbsp2, ['label', [['input', {
624
- name: il('interlintitle'),
625
- type: 'radio',
626
- value: '1',
627
- checked: $p.get('interlintitle') !== '0'
628
- }], ld('yes'), nbsp3]], ['label', [['input', {
629
- name: il('interlintitle'),
630
- type: 'radio',
631
- value: '0',
632
- checked: $p.get('interlintitle') === '0'
633
- }], ld('no')]]]], ['label', [ld('interlintitle_css'), nbsp2, ['input', {
634
- name: il('interlintitle_css'),
635
- type: 'text',
636
- value: $p.get('interlintitle_css') || '',
637
- size: '12'
638
- }]]], ['br'],
639
- /*
640
- ['br'],
641
- ['label', [
642
- ['input', {
643
- name: il('transpose'),
644
- type: 'checkbox',
645
- value: l('yes'),
646
- checked: $p.get('transpose') === l('yes')
647
- }],
648
- nbsp2, ld('transpose')
649
- ]],
650
- */
651
- ['br'], le('pageformatting_tips', 'h3', 'title', {}, [ld('pageformatting')]),
652
- /*
653
- ['label', [
654
- ld('speech_controls'), nbsp2,
655
- ['label', [
656
- ['input', {
657
- name: il('speech'),
658
- type: 'radio',
659
- value: '1',
660
- checked: $p.get('speech') === '1'
661
- }],
662
- ld('yes'), nbsp3
663
- ]],
664
- ['label', [
665
- ['input', {
666
- name: il('speech'),
667
- type: 'radio',
668
- value: '0',
669
- checked: $p.get('speech') !== '1'
670
- }],
671
- ld('no')
672
- ]]
673
- ]],
674
- ['br'],
675
- */
676
- ['label', [ld('page_css'), nbsp2, ['textarea', {
677
- name: il('pagecss'),
678
- title: l('page_css_tips'),
679
- value: $p.get('pagecss')
680
- }]]], ['br'], le('outputmode_tips', 'label', 'title', {}, [ld('outputmode'), nbsp2, // Todo: Could i18nize, but would need smaller values
681
- ['select', {
682
- name: il('outputmode')
683
- }, ['table', 'div' // , 'json-array',
684
- // 'json-object'
685
- ].map(mode => {
686
- const atts = {
687
- value: mode
688
- };
689
-
690
- if ($p.get('outputmode') === mode) {
691
- atts.selected = 'selected';
692
- }
693
-
694
- return lo(['param_values', 'outputmode', mode], atts);
695
- })]])]],
696
-
697
- addRandomFormFields({
698
- il,
699
- ld,
700
- l,
701
- le,
702
- $p,
703
- serializeParamsAsURL,
704
- content
705
- }) {
706
- const addRowContent = rowContent => {
707
- if (!rowContent || !rowContent.length) {
708
- return;
709
- }
710
-
711
- content.push(['tr', rowContent]);
712
- };
713
-
714
- [[['td', {
715
- colspan: 12,
716
- align: 'center'
717
- }, [['br'], ld('or'), ['br'], ['br']]]], [['td', {
718
- colspan: 12,
719
- align: 'center'
720
- }, [// Todo: Could allow random with fixed starting and/or ending range
721
- ['label', [ld('rnd'), nbsp3, ['input', {
722
- id: 'rand',
723
- name: il('rand'),
724
- type: 'checkbox',
725
- value: l('yes'),
726
- checked: $p.get('rand') === l('yes')
727
- }]]], nbsp3, ['label', [ld('verses-context'), jamilih.nbsp, ['input', {
728
- name: il('context'),
729
- type: 'number',
730
- min: 1,
731
- size: 4,
732
- value: $p.get('context')
733
- }]]], nbsp3, le('view-random-URL', 'input', 'value', {
734
- type: 'button',
735
- $on: {
736
- click() {
737
- const url = serializeParamsAsURL(_objectSpread2(_objectSpread2({}, getDataForSerializingParamsAsURL()), {}, {
738
- type: 'randomResult'
739
- }));
740
- jamilih.$('#randomURL').value = url;
741
- }
742
-
743
- }
744
- }), ['input', {
745
- id: 'randomURL',
746
- type: 'text'
747
- }]]]]].forEach(addRowContent);
748
- },
749
-
750
- getPreferences: ({
751
- languageParam,
752
- lf,
753
- paramsSetter,
754
- replaceHash,
755
- getFieldAliasOrNames,
756
- work,
757
- langs,
758
- imfl,
759
- l,
760
- localizeParamNames,
761
- namespace,
762
- hideFormattingSection,
763
- groups,
764
- preferencesPlugin
765
- }) => ['div', {
766
- style: {
767
- textAlign: 'left'
768
- },
769
- id: 'preferences',
770
- hidden: 'true'
771
- }, [['div', {
772
- style: 'margin-top: 10px;'
773
- }, [['label', [l('localizeParamNames'), ['input', {
774
- id: 'localizeParamNames',
775
- type: 'checkbox',
776
- checked: localizeParamNames,
777
- $on: {
778
- change({
779
- target: {
780
- checked
781
- }
782
- }) {
783
- localStorage.setItem(namespace + '-localizeParamNames', checked);
784
- }
785
-
786
- }
787
- }]]]]], ['div', [['label', [l('Hide formatting section'), ['input', {
788
- id: 'hideFormattingSection',
789
- type: 'checkbox',
790
- checked: hideFormattingSection,
791
- $on: {
792
- change({
793
- target: {
794
- checked
795
- }
796
- }) {
797
- jamilih.$('#advancedformatting').style.display = checked ? 'none' : 'block';
798
- localStorage.setItem(namespace + '-hideFormattingSection', checked);
799
- }
800
-
801
- }
802
- }]]]]], ['div', [['label', {
803
- for: 'prefLangs'
804
- }, [l('Preferred language(s)')]], ['br'], ['select', {
805
- id: 'prefLangs',
806
- multiple: 'multiple',
807
- size: langs.length,
808
- $on: {
809
- change({
810
- target: {
811
- selectedOptions
812
- }
813
- }) {
814
- // Todo: EU disclaimer re: storage?
815
- localStorage.setItem(namespace + '-langCodes', JSON.stringify([...selectedOptions].map(opt => opt.value)));
816
- }
817
-
818
- }
819
- }, langs.map(lan => {
820
- let langCodes = localStorage.getItem(namespace + '-langCodes');
821
- langCodes = langCodes && JSON.parse(langCodes);
822
- const atts = {
823
- value: lan.code
824
- };
825
-
826
- if (langCodes && langCodes.includes(lan.code)) {
827
- atts.selected = 'selected';
828
- }
829
-
830
- return ['option', atts, [imfl(['languages', lan.code])]];
831
- })]]], preferencesPlugin ? preferencesPlugin({
832
- $: jamilih.$,
833
- l,
834
- jml: jamilih.jml,
835
- paramsSetter,
836
- getDataForSerializingParamsAsURL,
837
- work,
838
- replaceHash,
839
- getFieldAliasOrNames
840
- }) : '']],
841
-
842
- addBrowseFields({
843
- browseFields,
844
- fieldInfo,
845
- ld,
846
- i,
847
- iil,
848
- $p,
849
- content
850
- }) {
851
- const work = $p.get('work');
852
-
853
- const addRowContent = rowContent => {
854
- if (!rowContent || !rowContent.length) {
855
- return;
856
- }
857
-
858
- content.push(['tr', rowContent]);
859
- };
860
-
861
- [// Todo: Separate formatting to CSS
862
- i > 0 ? [['td', {
863
- colspan: 12,
864
- align: 'center'
865
- }, [['br'], ld('or'), ['br'], ['br']]]] : '', [...(() => {
866
- const addBrowseFieldSet = setType => browseFields.reduce((rowContent, {
867
- fieldName,
868
- aliases,
869
- fieldSchema: {
870
- minimum,
871
- maximum
872
- }
873
- }, j) => {
874
- // Namespace by work for sake of browser auto-complete caching
875
- const name = work + '-' + iil(setType) + (i + 1) + '-' + (j + 1);
876
- const id = name;
877
- rowContent['#'].push(['td', [['label', {
878
- for: name
879
- }, [fieldName]]]], ['td', [aliases ? ['datalist', {
880
- id: 'dl-' + id
881
- }, aliases.map(alias => ['option', [alias]])] : '', aliases ? ['input', {
882
- name,
883
- id,
884
- class: 'browseField',
885
- list: 'dl-' + id,
886
- value: $p.get(name, true),
887
- $on: setType === 'start' ? {
888
- change(e) {
889
- jamilih.$$('input.browseField').forEach(bf => {
890
- if (bf.id.includes(i + 1 + '-' + (j + 1))) {
891
- bf.value = e.target.value;
892
- }
893
- });
894
- }
895
-
896
- } : undefined
897
- }] : ['input', {
898
- name,
899
- id,
900
- type: 'number',
901
- min: minimum,
902
- max: maximum,
903
- value: $p.get(name, true)
904
- }], nbsp3]]);
905
- return rowContent;
906
- }, {
907
- '#': []
908
- });
909
-
910
- return [addBrowseFieldSet('start'), ['td', [['b', [ld('to')]], nbsp3]], addBrowseFieldSet('end')];
911
- })(), ['td', [browseFields.length > 1 ? ld('versesendingdataoptional') : '']]], [['td', {
912
- colspan: 4 * browseFields.length + 2 + 1,
913
- align: 'center'
914
- }, [['table', [['tr', [browseFields.reduce((rowContent, {
915
- fieldName,
916
- aliases,
917
- fieldSchema: {
918
- minimum,
919
- maximum
920
- }
921
- }, j) => {
922
- // Namespace by work for sake of browser auto-complete caching
923
- const name = work + '-' + iil('anchor') + (i + 1) + '-' + (j + 1);
924
- const id = name;
925
- rowContent['#'].push(['td', [['label', {
926
- for: name
927
- }, [fieldName]]]], ['td', [aliases ? ['datalist', {
928
- id: 'dl-' + id
929
- }, aliases.map(alias => ['option', [alias]])] : '', aliases ? ['input', {
930
- name,
931
- id,
932
- class: 'browseField',
933
- list: 'dl-' + id,
934
- value: $p.get(name, true)
935
- }] : ['input', {
936
- name,
937
- id,
938
- type: 'number',
939
- min: minimum,
940
- max: maximum,
941
- value: $p.get(name, true)
942
- }], nbsp2]]);
943
- return rowContent;
944
- }, {
945
- '#': [['td', {
946
- style: 'font-weight: bold; vertical-align: bottom;'
947
- }, [ld('anchored-at') + nbsp3]]]
948
- }), ['td', [['label', [ld('field') + nbsp2, ['select', {
949
- name: iil('anchorfield') + (i + 1),
950
- size: '1'
951
- }, fieldInfo.map(({
952
- fieldAliasOrName
953
- }) => {
954
- const val = $p.get(iil('anchorfield') + (i + 1), true);
955
-
956
- if (val === fieldAliasOrName) {
957
- return ['option', {
958
- selected: true
959
- }, [fieldAliasOrName]];
960
- }
961
-
962
- return ['option', [fieldAliasOrName]];
963
- })]]]]]]]]]]]]].forEach(addRowContent);
964
- },
965
-
966
- main({
967
- lf,
968
- languageParam,
969
- l,
970
- namespace,
971
- heading,
972
- fallbackDirection,
973
- imfl,
974
- langs,
975
- fieldInfo,
976
- localizeParamNames,
977
- serializeParamsAsURL,
978
- paramsSetter,
979
- replaceHash,
980
- getFieldAliasOrNames,
981
- hideFormattingSection,
982
- $p,
983
- metadataObj,
984
- il,
985
- le,
986
- ld,
987
- iil,
988
- fieldMatchesLocale,
989
- preferredLocale,
990
- schemaItems,
991
- content,
992
- groups,
993
- preferencesPlugin
994
- }) {
995
- const work = $p.get('work');
996
-
997
- const serializeParamsAsURLWithData = ({
998
- type
999
- }) => {
1000
- return serializeParamsAsURL(_objectSpread2(_objectSpread2({}, getDataForSerializingParamsAsURL()), {}, {
1001
- type
1002
- }));
1003
- };
1004
-
1005
- const lo = (key, atts) => ['option', atts, [l({
1006
- key,
1007
-
1008
- fallback({
1009
- message
1010
- }) {
1011
- atts.dir = fallbackDirection;
1012
- return message;
1013
- }
1014
-
1015
- })]]; // Returns element with localized or fallback attribute value (as Jamilih);
1016
- // also adds direction
1017
-
1018
-
1019
- jamilih.jml('div', {
1020
- class: 'focus'
1021
- }, [['div', {
1022
- style: 'float: left;'
1023
- }, [['button', {
1024
- $on: {
1025
- click() {
1026
- const prefs = jamilih.$('#preferences');
1027
- prefs.hidden = !prefs.hidden;
1028
- }
1029
-
1030
- }
1031
- }, [l('Preferences')]], Templates.workDisplay.getPreferences({
1032
- languageParam,
1033
- lf,
1034
- paramsSetter,
1035
- replaceHash,
1036
- getFieldAliasOrNames,
1037
- work,
1038
- langs,
1039
- imfl,
1040
- l,
1041
- localizeParamNames,
1042
- namespace,
1043
- groups,
1044
- hideFormattingSection,
1045
- preferencesPlugin
1046
- })]], ['h2', [heading]], ['br'], ['form', {
1047
- id: 'browse',
1048
- $custom: {
1049
- $submit() {
1050
- const thisParams = serializeParamsAsURLWithData({
1051
- type: 'saveSettings'
1052
- }).replace(/^[^#]*#/, ''); // Don't change the visible URL
1053
-
1054
- console.log('history thisParams', thisParams);
1055
- history.replaceState(thisParams, document.title, location.href);
1056
- const newURL = serializeParamsAsURLWithData({
1057
- type: 'result'
1058
- });
1059
- location.href = newURL;
1060
- }
1061
-
1062
- },
1063
- $on: {
1064
- keydown({
1065
- key,
1066
- target
1067
- }) {
1068
- // Chrome is not having submit event triggered now with enter key
1069
- // presses on inputs, despite having a `type=submit` input in the
1070
- // form, and despite not using `preventDefault`
1071
- if (key === 'Enter' && target.localName.toLowerCase() !== 'textarea') {
1072
- this.$submit();
1073
- }
1074
- },
1075
-
1076
- submit(e) {
1077
- e.preventDefault();
1078
- this.$submit();
1079
- }
1080
-
1081
- },
1082
- name: il('browse')
1083
- }, [['table', {
1084
- align: 'center'
1085
- }, content], ['br'], ['div', {
1086
- style: 'margin-left: 20px'
1087
- }, [['br'], ['br'], ['table', {
1088
- border: '1',
1089
- align: 'center',
1090
- cellpadding: '5'
1091
- }, [['tr', {
1092
- valign: 'top'
1093
- }, [['td', [Templates.workDisplay.columnsTable({
1094
- ld,
1095
- fieldInfo,
1096
- $p,
1097
- le,
1098
- iil,
1099
- l,
1100
- metadataObj,
1101
- preferredLocale,
1102
- schemaItems,
1103
- fieldMatchesLocale
1104
- }), le('save-settings-URL', 'input', 'value', {
1105
- type: 'button',
1106
- $on: {
1107
- click() {
1108
- const url = serializeParamsAsURLWithData({
1109
- type: 'saveSettings'
1110
- });
1111
- jamilih.$('#settings-URL').value = url;
1112
- }
1113
-
1114
- }
1115
- }), ['input', {
1116
- id: 'settings-URL'
1117
- }], ['br'], ['button', {
1118
- $on: {
1119
- async click(e) {
1120
- e.preventDefault();
1121
- const paramsCopy = paramsSetter(_objectSpread2(_objectSpread2({}, getDataForSerializingParamsAsURL()), {}, {
1122
- workName: work,
1123
- // Delete work of current page
1124
- type: 'startEndResult'
1125
- }));
1126
- const url = replaceHash(paramsCopy) + `&work=${work}&${work}-startEnd1=%s`; // %s will be escaped if set as param; also add changeable workName here
1127
-
1128
- try {
1129
- await navigator.clipboard.writeText(url);
1130
- } catch (err) {// User rejected
1131
- }
1132
- }
1133
-
1134
- }
1135
- }, [l('Copy_shortcut_URL')]]]], Templates.workDisplay.advancedFormatting({
1136
- ld,
1137
- il,
1138
- l,
1139
- lo,
1140
- le,
1141
- $p,
1142
- hideFormattingSection
1143
- })
1144
- /*
1145
- // Todo: Is this still the case? No way to control with CSS?
1146
- ,arabicContent ?
1147
- // If there is Arabic content, a text box will be created for
1148
- // each field with such content to allow the user to choose
1149
- // how wide the field should be (since the Arabic is smaller).
1150
- // Todo: Allow naming of the field differently for Persian?
1151
- // Allowing any column to be resized would probably be most
1152
- // consistent with this project's aim to not make arbitrary
1153
- // decisions on what should be customizable, but rather make
1154
- // as much as possible customizable. It may also be helpful
1155
- // for Chinese, etc. If adding, also need $p.get() for
1156
- // defaulting behavior
1157
- {'#': arabicContent.map((item, i) =>
1158
- {'#': [
1159
- 'Width of Arabic column: ', // Todo: i18n
1160
- ['input', {
1161
- name: il('arw') + i,
1162
- type: 'text',
1163
- value: '',
1164
- size: '7',
1165
- maxlength: '12'
1166
- }]
1167
- ]}
1168
- )} :
1169
- ''
1170
- */
1171
- ]]]]]], ['p', {
1172
- align: 'center'
1173
- }, [le('submitgo', 'input', 'value', {
1174
- type: 'submit'
1175
- })]]]]], jamilih.$('#main'));
1176
- }
1177
-
1178
- };
1179
-
1180
- var resultsDisplayServerOrClient$1 = {
1181
- caption({
1182
- heading,
1183
- ranges
1184
- }) {
1185
- return heading + ' ' + ranges;
1186
- },
1187
-
1188
- startSeparator({
1189
- l
1190
- }) {
1191
- return l('colon');
1192
- },
1193
-
1194
- innerBrowseFieldSeparator({
1195
- l
1196
- }) {
1197
- return l('comma-space');
1198
- },
1199
-
1200
- ranges({
1201
- l,
1202
- startRange,
1203
- endVals,
1204
- rangeNames
1205
- }) {
1206
- return startRange + // l('to').toLowerCase() + ' ' +
1207
- '-' + endVals.join(Templates.resultsDisplayServerOrClient.startSeparator({
1208
- l
1209
- })) + ' (' + rangeNames + ')';
1210
- },
1211
-
1212
- fieldValueAlias({
1213
- key,
1214
- value
1215
- }) {
1216
- return value + ' (' + key + ')';
1217
- },
1218
-
1219
- interlinearSegment({
1220
- lang,
1221
- dir,
1222
- html
1223
- }) {
1224
- return `<span${lang ? ` lang="${lang}"` : ''}${dir ? ` dir="${dir}"` : ''}>${html}</span>`;
1225
- },
1226
-
1227
- interlinearTitle({
1228
- l,
1229
- val
1230
- }) {
1231
- const colonSpace = l('colon-space');
1232
- return `<span class="interlintitle">${val}</span>${colonSpace}`;
1233
- },
1234
-
1235
- styles({
1236
- $p,
1237
- $pRaw,
1238
- $pRawEsc,
1239
- $pEscArbitrary,
1240
- escapeQuotedCSS,
1241
- escapeCSS,
1242
- tableWithFixedHeaderAndFooter,
1243
- checkedFieldIndexes,
1244
- hasCaption
1245
- }) {
1246
- const colorEsc = !$p.has('color', true) || $p.get('color', true) === '#' ? $pRawEsc('colorName') : $pEscArbitrary('color');
1247
- const bgcolorEsc = !$p.has('bgcolor', true) || $p.get('bgcolor', true) === '#' ? $pRawEsc('bgcolorName') : $pEscArbitrary('bgcolor');
1248
- const tableHeight = '100%';
1249
- const topToCaptionStart = 1;
1250
- const captionSizeDelta = (hasCaption ? 2 : 0) + 0;
1251
- const topToHeadingsStart = topToCaptionStart + captionSizeDelta;
1252
- const headingsDelta = 1;
1253
- const topToBodyStart = topToHeadingsStart + headingsDelta + 'em';
1254
- const footerHeight = '2em';
1255
- const bodyToFooterPadding = '0.5em';
1256
- const topToBodyEnd = `100% - ${topToBodyStart} - ${footerHeight} - ${bodyToFooterPadding}`;
1257
- const topToBodyEndCalc = `calc(${topToBodyEnd})`;
1258
- const topToFooter = `calc(${topToBodyEnd} + ${bodyToFooterPadding})`;
1259
- return ['style', [(tableWithFixedHeaderAndFooter ? `
1260
- html, body, #main, #main > div {
1261
- height: 100%; /* Needed to ensure descendent heights retain 100%; could be avoided if didn't want percent on table height */
1262
- overflow-y: hidden; /* Not sure why we're getting extra here, but... */
1263
- }
1264
- .anchor-table-header {
1265
- background-color: ${bgcolorEsc || 'white'}; /* Header background (not just for div text inside header, but for whole header area) */
1266
- overflow-x: hidden; /* Not sure why we're getting extra here, but... */
1267
- position: relative; /* Ensures we are still flowing, but provides anchor for absolutely positioned thead below (absolute positioning positions relative to nearest non-static ancestor; clear demo at https://www.w3schools.com/cssref/playit.asp?filename=playcss_position&preval=fixed ) */
1268
- padding-top: ${topToBodyStart}; /* Provides space for the header (and caption) (the one that is absolutely positioned below relative to here) */
1269
- height: ${tableHeight}; /* Percent of the whole screen taken by the table */
1270
- }
1271
- .anchor-table-body {
1272
- overflow-y: auto; /* Provides scrollbars when text fills up beyond the height */
1273
- height: ${topToBodyEndCalc}; /* If < 100%, the header anchor background color will seep below table */
1274
- }
1275
-
1276
- .caption, .thead .th, .tfoot .th {
1277
- line-height: 0; /* th div will have own line-height; reducing here avoids fattening it by an inner div */
1278
- color: transparent; /* Hides the non-div duplicated header text */
1279
- white-space: nowrap; /* Ensures column header text uses up full width without overlap (esp. wrap no longer seems to work); this must be applied outside of the div */
1280
- border: none; /* We don't want a border for this invisible section */
1281
- }
1282
- div.inner-caption, .thead .th div.th-inner, .tfoot .th div.th-inner { /* divs are used as th is supposedly problematic */
1283
- position: absolute; /* Puts relative to nearest non-static ancestor (our relative header anchor) */
1284
- color: initial; /* Header must have an explicit color or it will get transparent container color */
1285
- line-height: normal; /* Revert ancestor line height of 0 */
1286
- }
1287
- .thead .th div.th-inner {
1288
- top: ${topToHeadingsStart}em; /* Ensures our header stays fixed at top outside of normal flow of table */
1289
- }
1290
- div.inner-caption {
1291
- top: ${topToCaptionStart}em;
1292
- }
1293
- .tfoot .th div.th-inner { /* divs are used as th is supposedly problematic */
1294
- top: ${topToFooter}; /* Ensures our header stays fixed at top outside of normal flow of table */
1295
- }
1296
- .zupa div.zupa1 {
1297
- margin: 0 auto !important;
1298
- width: 0 !important;
1299
- }
1300
- .zupa div.th-inner, .zupa div.inner-caption {
1301
- width: 100%;
1302
- margin-left: -50%;
1303
- text-align: center;
1304
- }
1305
- ` : '') + ($pRaw('caption') === 'y' ? tableWithFixedHeaderAndFooter ? '.caption div.inner-caption, ' : '.caption, ' : '') + ($pRaw('header') === 'y' ? tableWithFixedHeaderAndFooter ? `` // `.thead .th, .thead .th div.th-inner, ` // Problems at least in Chrome
1306
- : `.thead .th, ` : '') + ($pRaw('footer') === 'y' ? tableWithFixedHeaderAndFooter ? `` // `.tfoot .th, .tfoot .th div.th-inner, ` // Problems at least in Chrome
1307
- : `.tfoot .th, ` : '') + '.tbody td' + ` {
1308
- vertical-align: top;
1309
- font-style: ${$pRawEsc('fontstyle')};
1310
- font-variant: ${$pRawEsc('fontvariant')};
1311
- font-weight: ${$pEscArbitrary('fontweight')};
1312
- ${$pEscArbitrary('fontsize') ? `font-size: ${$pEscArbitrary('fontsize')};` : ''}
1313
- font-family: ${$pEscArbitrary('fontSeq')};
1314
-
1315
- font-stretch: ${$pRawEsc('fontstretch')};
1316
- letter-spacing: ${$pEscArbitrary('letterspacing')};
1317
- line-height: ${$pEscArbitrary('lineheight')};
1318
- ${colorEsc ? `color: ${escapeCSS(colorEsc)};` : ''}
1319
- ${bgcolorEsc ? `background-color: ${escapeCSS(bgcolorEsc)};` : ''}
1320
- }
1321
- ${escapeCSS($pEscArbitrary('pagecss') || '')}
1322
- ` + checkedFieldIndexes.map((idx, i) => ($pRaw('header') === 'y' ? tableWithFixedHeaderAndFooter ? `.thead .th:nth-child(${i + 1}) div.th-inner, ` : `.thead .th:nth-child(${i + 1}), ` : '') + ($pRaw('footer') === 'y' ? tableWithFixedHeaderAndFooter ? `.tfoot .th:nth-child(${i + 1}) div.th-inner, ` : `.tfoot .th:nth-child(${i + 1}), ` : '') + `.tbody td:nth-child(${i + 1}) ` + `{
1323
- ${$pEscArbitrary('css' + (idx + 1))}
1324
- }
1325
- `).join('') + ($pEscArbitrary('interlintitle_css') ? `
1326
- /* http://salzerdesign.com/test/fixedTable.html */
1327
- .interlintitle {
1328
- ${escapeCSS($pEscArbitrary('interlintitle_css'))}
1329
- }
1330
- ` : '') + (bgcolorEsc ? `
1331
- body {
1332
- background-color: ${bgcolorEsc};
1333
- }
1334
- ` : '')]];
1335
- },
1336
-
1337
- main({
1338
- tableData,
1339
- $p,
1340
- $pRaw,
1341
- $pRawEsc,
1342
- $pEscArbitrary,
1343
- // Todo: escaping should be done in business logic!
1344
- escapeQuotedCSS,
1345
- escapeCSS,
1346
- escapeHTML,
1347
- l,
1348
- localizedFieldNames,
1349
- fieldLangs,
1350
- fieldDirs,
1351
- caption,
1352
- hasCaption,
1353
- showInterlinTitles,
1354
- determineEnd,
1355
- getCanonicalID,
1356
- canonicalBrowseFieldSetName,
1357
- getCellValue,
1358
- checkedAndInterlinearFieldInfo,
1359
- interlinearSeparator = '<br /><br />'
1360
- }) {
1361
- const tableOptions = {
1362
- table: [['table', {
1363
- class: 'table',
1364
- border: $pRaw('border') || '0'
1365
- }], ['tr', {
1366
- class: 'tr'
1367
- }], ['td'], // , {class: 'td'} // Too much data to add class to each
1368
- ['th', {
1369
- class: 'th'
1370
- }], ['caption', {
1371
- class: 'caption'
1372
- }], ['thead', {
1373
- class: 'thead'
1374
- }], ['tbody', {
1375
- class: 'tbody'
1376
- }], ['tfoot', {
1377
- class: 'tfoot'
1378
- }] // ['colgroup', {class: 'colgroup'}],
1379
- // ['col', {class: 'col'}]
1380
- ],
1381
- div: [['div', {
1382
- class: 'table',
1383
- style: 'display: table;'
1384
- }], ['div', {
1385
- class: 'tr',
1386
- style: 'display: table-row;'
1387
- }], ['div', {
1388
- class: 'td',
1389
- style: 'display: table-cell;'
1390
- }], ['div', {
1391
- class: 'th',
1392
- style: 'display: table-cell;'
1393
- }], ['div', {
1394
- class: 'caption',
1395
- style: 'display: table-caption;'
1396
- }], ['div', {
1397
- class: 'thead',
1398
- style: 'display: table-header-group;'
1399
- }], ['div', {
1400
- class: 'tbody',
1401
- style: 'display: table-row-group;'
1402
- }], ['div', {
1403
- class: 'tfoot',
1404
- style: 'display: table-footer-group;'
1405
- }] // ['div', {class: 'colgroup', style: 'display: table-column-group;'}],
1406
- // ['div', {class: 'col', style: 'display: table-column;'}]
1407
- ],
1408
- 'json-array': 'json',
1409
- 'json-object': 'json'
1410
- };
1411
- const outputmode = $p.get('outputmode', true); // Why not $pRaw?
1412
-
1413
- switch (outputmode) {
1414
- case 'json-object': // Can later fall through if supporting
1415
-
1416
- case 'json-array':
1417
- // tableOptions[outputmode] = tableOptions.table;
1418
- // break;
1419
- throw new Error('JSON object support is currently not available');
1420
- }
1421
-
1422
- const tableElems = tableOptions[Object.prototype.hasOwnProperty.call(tableOptions, outputmode) ? outputmode : 'table' // Default
1423
- ];
1424
- const [tableElem, trElem, tdElem, thElem, captionElem, theadElem, tbodyElem, tfootElem] = tableElems; // colgroupElem, colElem
1425
-
1426
- const [checkedFields, checkedFieldIndexes, allInterlinearColIndexes] = checkedAndInterlinearFieldInfo;
1427
- const tableWithFixedHeaderAndFooter = $pRaw('headerfooterfixed') === 'yes';
1428
-
1429
- const tableWrap = children => {
1430
- return tableWithFixedHeaderAndFooter ? ['div', {
1431
- class: 'table-responsive anchor-table-header zupa'
1432
- }, [['div', {
1433
- class: 'table-responsive anchor-table-body'
1434
- }, children]]] : ['div', {
1435
- class: 'table-responsive'
1436
- }, children];
1437
- };
1438
-
1439
- const addChildren = (el, children) => {
1440
- el = JSON.parse(JSON.stringify(el));
1441
- el.push(children);
1442
- return el;
1443
- };
1444
-
1445
- const addAtts = ([el, atts], newAtts) => [el, _objectSpread2(_objectSpread2({}, atts), newAtts)];
1446
-
1447
- const foundState = {
1448
- start: false,
1449
- end: false
1450
- };
1451
- const outArr = [];
1452
- const {
1453
- showEmptyInterlinear,
1454
- showTitleOnSingleInterlinear
1455
- } = this;
1456
-
1457
- const checkEmpty = (tdVal, htmlEscaped) => {
1458
- if (!showEmptyInterlinear) {
1459
- if (!htmlEscaped) {
1460
- tdVal = new DOMParser().parseFromString(tdVal, 'text/html').body;
1461
- [...tdVal.querySelectorAll('br')].forEach(br => {
1462
- br.remove();
1463
- });
1464
- tdVal = tdVal.innerHTML;
1465
- }
1466
-
1467
- if (!tdVal.trim()) {
1468
- return true;
1469
- }
1470
- }
1471
- };
1472
-
1473
- tableData.some((tr, i) => {
1474
- const rowID = determineEnd({
1475
- tr,
1476
- foundState
1477
- });
1478
-
1479
- if (typeof rowID === 'boolean') {
1480
- return rowID;
1481
- }
1482
-
1483
- const canonicalID = getCanonicalID({
1484
- tr
1485
- });
1486
- outArr.push(addChildren(addAtts(trElem, {
1487
- dataset: {
1488
- row: rowID,
1489
- 'canonical-type': canonicalBrowseFieldSetName,
1490
- 'canonical-id': canonicalID
1491
- }
1492
- }), checkedFieldIndexes.map((idx, j) => {
1493
- const interlinearColIndexes = allInterlinearColIndexes[j];
1494
- const showInterlins = interlinearColIndexes;
1495
- const {
1496
- tdVal,
1497
- htmlEscaped
1498
- } = getCellValue({
1499
- tr,
1500
- idx
1501
- });
1502
- const interlins = showInterlins && interlinearColIndexes.map(idx => {
1503
- // Need to get a new one
1504
- const {
1505
- tdVal,
1506
- htmlEscaped
1507
- } = getCellValue({
1508
- tr,
1509
- idx
1510
- });
1511
- console.log('showEmptyInterlinear', showEmptyInterlinear, htmlEscaped);
1512
- const isEmpty = checkEmpty(tdVal, htmlEscaped);
1513
-
1514
- if (isEmpty) {
1515
- return '';
1516
- }
1517
-
1518
- return showInterlins ? Templates.resultsDisplayServerOrClient.interlinearSegment({
1519
- lang: fieldLangs[idx],
1520
- dir: fieldDirs[idx],
1521
- html: (showInterlinTitles ? Templates.resultsDisplayServerOrClient.interlinearTitle({
1522
- l,
1523
- val: localizedFieldNames[idx]
1524
- }) : '') + tdVal
1525
- }) : tdVal;
1526
- }).filter(cell => cell !== '');
1527
- return addAtts(tdElem, {
1528
- // We could remove these (and add to <col>) for optimizing delivery,
1529
- // but non-table output couldn't use unless on a hidden element
1530
- // Can't have unique IDs if user duplicates a column
1531
- id: 'row' + (i + 1) + 'col' + (j + 1),
1532
- lang: fieldLangs[idx],
1533
- dir: fieldDirs[idx],
1534
- dataset: {
1535
- col: localizedFieldNames[idx]
1536
- },
1537
- innerHTML: (showInterlins && !checkEmpty(tdVal, htmlEscaped) && (showTitleOnSingleInterlinear || interlins.length) ? Templates.resultsDisplayServerOrClient.interlinearSegment({
1538
- lang: fieldLangs[idx],
1539
- html: (showInterlinTitles ? Templates.resultsDisplayServerOrClient.interlinearTitle({
1540
- l,
1541
- val: localizedFieldNames[idx]
1542
- }) : '') + tdVal
1543
- }) : tdVal) + (interlinearColIndexes && interlins.length ? interlinearSeparator + interlins.join(interlinearSeparator) : '')
1544
- });
1545
- })));
1546
- return false;
1547
- });
1548
- return ['div', [Templates.resultsDisplayServerOrClient.styles({
1549
- $p,
1550
- $pRaw,
1551
- $pRawEsc,
1552
- $pEscArbitrary,
1553
- escapeQuotedCSS,
1554
- escapeCSS,
1555
- tableWithFixedHeaderAndFooter,
1556
- checkedFieldIndexes,
1557
- hasCaption
1558
- }), tableWrap([addChildren(tableElem, [caption ? addChildren(captionElem, [caption, tableWithFixedHeaderAndFooter ? ['div', {
1559
- class: 'zupa1'
1560
- }, [['div', {
1561
- class: 'inner-caption'
1562
- }, [['span', [caption]]]]]] : '']) : '',
1563
- /*
1564
- // Works but quirky, e.g., `color` doesn't work (as also
1565
- // confirmed per https://quirksmode.org/css/css2/columns.html)
1566
- addChildren(colgroupElem,
1567
- checkedFieldIndexes.map((idx, i) =>
1568
- addAtts(colElem, {style: $pRaw('css' + (idx + 1))})
1569
- )
1570
- ),
1571
- */
1572
- $pRaw('header') !== '0' ? addChildren(theadElem, [addChildren(trElem, checkedFields.map((cf, i) => {
1573
- const interlinearColIndexes = allInterlinearColIndexes[i];
1574
- cf = escapeHTML(cf) + (interlinearColIndexes ? l('comma-space') + interlinearColIndexes.map(idx => localizedFieldNames[idx]).join(l('comma-space')) : '');
1575
- return addChildren(thElem, [cf, tableWithFixedHeaderAndFooter ? ['div', {
1576
- class: 'zupa1'
1577
- }, [['div', {
1578
- class: 'th-inner'
1579
- }, [['span', [cf]]]]]] : '']);
1580
- }))]) : '', $pRaw('footer') && $pRaw('footer') !== '0' ? addChildren(tfootElem, [addChildren(trElem, checkedFields.map((cf, i) => {
1581
- const interlinearColIndexes = allInterlinearColIndexes[i];
1582
- cf = escapeHTML(cf) + (interlinearColIndexes ? l('comma-space') + interlinearColIndexes.map(idx => localizedFieldNames[idx]).join(l('comma-space')) : '');
1583
- return addChildren(thElem, [cf, tableWithFixedHeaderAndFooter ? ['div', {
1584
- class: 'zupa1'
1585
- }, [['div', {
1586
- class: 'th-inner'
1587
- }, [['span', [cf]]]]]] : '']);
1588
- }))]) : '', addChildren(tbodyElem, outArr)])])]];
1589
- }
1590
-
1591
- };
1592
-
1593
- const $ = sel => document.querySelector(sel);
1594
-
1595
- const $e = (el, descendentsSel) => {
1596
- el = typeof el === 'string' ? $(el) : el;
1597
- return el.querySelector(descendentsSel);
1598
- };
1599
-
1600
- const _excluded = ["submit", "submitClass"],
1601
- _excluded2 = ["submit", "cancel", "cancelClass", "submitClass"],
1602
- _excluded3 = ["message", "submit"];
1603
- const defaultLocale = 'en';
1604
- const localeStrings = {
1605
- en: {
1606
- submit: 'Submit',
1607
- cancel: 'Cancel',
1608
- ok: 'Ok'
1609
- }
1610
- };
1611
-
1612
- class Dialog {
1613
- constructor({
1614
- locale,
1615
- localeObject
1616
- } = {}) {
1617
- this.setLocale({
1618
- locale,
1619
- localeObject
1620
- });
1621
- }
1622
-
1623
- setLocale({
1624
- locale = {},
1625
- localeObject = {}
1626
- }) {
1627
- this.localeStrings = _objectSpread2(_objectSpread2(_objectSpread2({}, localeStrings[defaultLocale]), localeStrings[locale]), localeObject);
1628
- }
1629
-
1630
- makeDialog({
1631
- atts = {},
1632
- children = [],
1633
- close,
1634
- remove = true
1635
- }) {
1636
- if (close) {
1637
- if (!atts.$on) {
1638
- atts.$on = {};
1639
- }
1640
-
1641
- if (!atts.$on.close) {
1642
- atts.$on.close = close;
1643
- }
1644
- }
1645
-
1646
- const dialog = jamilih.jml('dialog', atts, children, jamilih.$('#main'));
1647
- dialog.showModal();
1648
-
1649
- if (remove) {
1650
- dialog.addEventListener('close', () => {
1651
- dialog.remove();
1652
- });
1653
- }
1654
-
1655
- return dialog;
1656
- }
1657
-
1658
- makeSubmitDialog(_ref) {
1659
- let {
1660
- submit,
1661
- // Don't pass this on to `args` if present
1662
- submitClass = 'submit'
1663
- } = _ref,
1664
- args = _objectWithoutProperties(_ref, _excluded);
1665
-
1666
- const dialog = this.makeCancelDialog(args);
1667
- $e(dialog, `button.${args.cancelClass || 'cancel'}`).before(jamilih.jml('button', {
1668
- class: submitClass,
1669
- $on: {
1670
- click(e) {
1671
- if (submit) {
1672
- submit.call(this, {
1673
- e,
1674
- dialog
1675
- });
1676
- }
1677
- }
1678
-
1679
- }
1680
- }, [this.localeStrings.submit]), jamilih.nbsp.repeat(2));
1681
- return dialog;
1682
- }
1683
-
1684
- makeCancelDialog(_ref2) {
1685
- let {
1686
- submit,
1687
- // Don't pass this on to `args` if present
1688
- cancel,
1689
- cancelClass = 'cancel',
1690
- submitClass = 'submit'
1691
- } = _ref2,
1692
- args = _objectWithoutProperties(_ref2, _excluded2);
1693
-
1694
- const dialog = this.makeDialog(args);
1695
- jamilih.jml('div', {
1696
- class: submitClass
1697
- }, [['br'], ['br'], ['button', {
1698
- class: cancelClass,
1699
- $on: {
1700
- click(e) {
1701
- e.preventDefault();
1702
-
1703
- if (cancel) {
1704
- if (cancel.call(this, {
1705
- e,
1706
- dialog
1707
- }) === false) {
1708
- return;
1709
- }
1710
- }
1711
-
1712
- dialog.close();
1713
- }
1714
-
1715
- }
1716
- }, [this.localeStrings.cancel]]], dialog);
1717
- return dialog;
1718
- }
1719
-
1720
- alert(message, ok) {
1721
- message = typeof message === 'string' ? {
1722
- message
1723
- } : message;
1724
- const {
1725
- ok: includeOk = typeof ok === 'object' ? ok.ok !== false : ok !== false,
1726
- message: msg,
1727
- submitClass = 'submit'
1728
- } = message;
1729
- return new Promise((resolve, reject) => {
1730
- const dialog = jamilih.jml('dialog', [msg, ...(includeOk ? [['br'], ['br'], ['div', {
1731
- class: submitClass
1732
- }, [['button', {
1733
- $on: {
1734
- click() {
1735
- dialog.close();
1736
- resolve();
1737
- }
1738
-
1739
- }
1740
- }, [this.localeStrings.ok]]]]] : [])], jamilih.$('#main'));
1741
- dialog.showModal();
1742
- });
1743
- }
1744
-
1745
- prompt(message) {
1746
- message = typeof message === 'string' ? {
1747
- message
1748
- } : message;
1749
-
1750
- const {
1751
- message: msg,
1752
- submit: userSubmit
1753
- } = message,
1754
- submitArgs = _objectWithoutProperties(message, _excluded3);
1755
-
1756
- return new Promise((resolve, reject) => {
1757
- const submit = function ({
1758
- e,
1759
- dialog
1760
- }) {
1761
- if (userSubmit) {
1762
- userSubmit.call(this, {
1763
- e,
1764
- dialog
1765
- });
1766
- }
1767
-
1768
- dialog.close();
1769
- resolve($e(dialog, 'input').value);
1770
- };
1771
- /* const dialog = */
1772
-
1773
-
1774
- this.makeSubmitDialog(_objectSpread2(_objectSpread2({}, submitArgs), {}, {
1775
- submit,
1776
-
1777
- cancel() {
1778
- reject(new Error('cancelled'));
1779
- },
1780
-
1781
- children: [['label', [msg, jamilih.nbsp.repeat(3), ['input']]]]
1782
- }));
1783
- });
1784
- }
1785
-
1786
- confirm(message) {
1787
- message = typeof message === 'string' ? {
1788
- message
1789
- } : message;
1790
- const {
1791
- message: msg,
1792
- submitClass = 'submit'
1793
- } = message;
1794
- return new Promise((resolve, reject) => {
1795
- const dialog = jamilih.jml('dialog', [msg, ['br'], ['br'], ['div', {
1796
- class: submitClass
1797
- }, [['button', {
1798
- $on: {
1799
- click() {
1800
- dialog.close();
1801
- resolve();
1802
- }
1803
-
1804
- }
1805
- }, [this.localeStrings.ok]], jamilih.nbsp.repeat(2), ['button', {
1806
- $on: {
1807
- click() {
1808
- dialog.close();
1809
- reject(new Error('cancelled'));
1810
- }
1811
-
1812
- }
1813
- }, [this.localeStrings.cancel]]]]], jamilih.$('#main'));
1814
- dialog.showModal();
1815
- });
1816
- }
1817
-
1818
- }
1819
-
1820
- const dialogs = new Dialog();
1821
-
1822
- var resultsDisplayClient = {
1823
- anchorRowCol({
1824
- anchorRowCol
1825
- }) {
1826
- return jamilih.$('#' + anchorRowCol);
1827
- },
1828
-
1829
- anchors({
1830
- escapedRow,
1831
- escapedCol
1832
- }) {
1833
- const sel = 'tr[data-row="' + escapedRow + '"]' + (escapedCol ? '> td[data-col="' + escapedCol + '"]' : '');
1834
- return jamilih.$(sel);
1835
- },
1836
-
1837
- main(...args) {
1838
- let html;
1839
-
1840
- try {
1841
- html = Templates.resultsDisplayServerOrClient.main(...args);
1842
- } catch (err) {
1843
- if (err.message === 'JSON support is currently not available') {
1844
- dialogs.alert(err.message);
1845
- }
1846
- }
1847
-
1848
- jamilih.jml(...html, jamilih.$('#main'));
1849
- }
1850
-
1851
- };
1852
-
1853
- /* eslint-env browser */
1854
- const Templates = {
1855
- languageSelect,
1856
- workSelect,
1857
- workDisplay,
1858
- resultsDisplayServerOrClient: resultsDisplayServerOrClient$1,
1859
- resultsDisplayClient,
1860
-
1861
- defaultBody() {
1862
- // We empty rather than `replaceWith` as our Jamilih `body`
1863
- // aliases expect the old instance
1864
- while (jamilih.body.hasChildNodes()) {
1865
- jamilih.body.firstChild.remove();
1866
- }
1867
-
1868
- return jamilih.jml('div', {
1869
- id: 'main',
1870
- role: 'main'
1871
- }, jamilih.body);
1872
- }
1873
-
1874
- };
1875
- Templates.permissions = {
1876
- versionChange() {
1877
- jamilih.$('#versionChange').showModal();
1878
- },
1879
-
1880
- addLogEntry({
1881
- text
1882
- }) {
1883
- const installationDialog = jamilih.$('#installationLogContainer');
1884
-
1885
- try {
1886
- installationDialog.showModal();
1887
- const container = jamilih.$('#dialogContainer');
1888
- container.hidden = false;
1889
- } catch (err) {// May already be open
1890
- }
1891
-
1892
- jamilih.$('#installationLog').append(text + '\n');
1893
- },
1894
-
1895
- exitDialogs() {
1896
- const container = jamilih.$('#dialogContainer');
1897
-
1898
- if (container) {
1899
- container.hidden = true;
1900
- }
1901
- },
1902
-
1903
- dbError({
1904
- type,
1905
- escapedErrorMessage
1906
- }) {
1907
- if (type) {
1908
- jamilih.jml('span', [type, ' ', escapedErrorMessage], jamilih.$('#dbError'));
1909
- }
1910
-
1911
- jamilih.$('#dbError').showModal();
1912
- },
1913
-
1914
- errorRegistering(escapedErrorMessage) {
1915
- if (escapedErrorMessage) {
1916
- jamilih.jml('span', [escapedErrorMessage], jamilih.$('#errorRegistering'));
1917
- }
1918
-
1919
- jamilih.$('#errorRegistering').showModal();
1920
- },
1921
-
1922
- browserNotGrantingPersistence() {
1923
- jamilih.$('#browserNotGrantingPersistence').showModal();
1924
- },
1925
-
1926
- main({
1927
- l,
1928
- ok,
1929
- refuse,
1930
- close,
1931
- closeBrowserNotGranting
1932
- }) {
1933
- const installationDialog = jamilih.jml('dialog', {
1934
- style: 'text-align: center; height: 100%',
1935
- id: 'installationLogContainer',
1936
- class: 'focus'
1937
- }, [['p', [l('wait-installing')]], ['div', {
1938
- style: 'height: 80%; overflow: auto;'
1939
- }, [['pre', {
1940
- id: 'installationLog'
1941
- }, []]]] // ['textarea', {readonly: true, style: 'width: 80%; height: 80%;'}]
1942
- ]);
1943
- let requestPermissionsDialog = '';
1944
-
1945
- if (ok) {
1946
- requestPermissionsDialog = jamilih.jml('dialog', {
1947
- id: 'willRequestStoragePermissions',
1948
- $on: {
1949
- close
1950
- }
1951
- }, [['section', [l('will-request-storage-permissions')]], ['br'], ['div', {
1952
- class: 'focus'
1953
- }, [['button', {
1954
- $on: {
1955
- click: ok
1956
- }
1957
- }, [l('OK')]], ['br'], ['br'], ['button', {
1958
- $on: {
1959
- click: refuse
1960
- }
1961
- }, [l('refuse-request-storage-permissions')]]]]]);
1962
- }
1963
-
1964
- const errorRegisteringNotice = jamilih.jml('dialog', {
1965
- id: 'errorRegistering'
1966
- }, [['section', [l('errorRegistering')]]]);
1967
- let browserNotGrantingPersistenceAlert = '';
1968
-
1969
- if (closeBrowserNotGranting) {
1970
- browserNotGrantingPersistenceAlert = jamilih.jml('dialog', {
1971
- id: 'browserNotGrantingPersistence'
1972
- }, [['section', [l('browser-not-granting-persistence')]], ['br'], ['div', {
1973
- class: 'focus'
1974
- }, [['button', {
1975
- $on: {
1976
- click: closeBrowserNotGranting
1977
- }
1978
- }, [l('OK')]]]]]);
1979
- }
1980
-
1981
- const versionChangeNotice = jamilih.jml('dialog', {
1982
- id: 'versionChange'
1983
- }, [['section', [l('versionChange')]]]);
1984
- const dbErrorNotice = jamilih.jml('dialog', {
1985
- id: 'dbError'
1986
- }, [['section', [l('dbError')]]]);
1987
- jamilih.jml('div', {
1988
- id: 'dialogContainer',
1989
- style: 'height: 100%'
1990
- }, [installationDialog, requestPermissionsDialog, browserNotGrantingPersistenceAlert, errorRegisteringNotice, versionChangeNotice, dbErrorNotice], jamilih.$('#main'));
1991
- return [installationDialog, requestPermissionsDialog, browserNotGrantingPersistenceAlert, errorRegisteringNotice, versionChangeNotice, dbErrorNotice];
1992
- }
1993
-
1994
- };
1995
-
1996
- const escapeHTML = s => {
1997
- return !s ? '' : s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
1998
- };
1999
-
2000
- /* eslint-env browser */
2001
- // Todo: remember this locales choice by cookie?
2002
- const getPreferredLanguages = ({
2003
- namespace,
2004
- preferredLocale
2005
- }) => {
2006
- // Todo: Add to this optionally with one-off tag input box
2007
- // Todo: Switch to fallbackLanguages so can default to
2008
- // navigator.languages?
2009
- const langCodes = localStorage.getItem(namespace + '-langCodes');
2010
- const lngs = langCodes && JSON.parse(langCodes) || [preferredLocale];
2011
- const langArr = [];
2012
- lngs.forEach(lng => {
2013
- // Todo: Check for multiple separate hyphenated
2014
- // groupings (for each supplied language)
2015
- const higherLocale = lng.replace(/-.*$/, '');
2016
-
2017
- if (higherLocale === lng) {
2018
- langArr.push(lng);
2019
- } else {
2020
- langArr.push(lng, higherLocale);
2021
- }
2022
- });
2023
- return langArr;
2024
- };
2025
- class Languages {
2026
- constructor({
2027
- langData
2028
- }) {
2029
- this.langData = langData;
2030
- }
2031
-
2032
- localeFromLangData(lan) {
2033
- return this.langData['localization-strings'][lan];
2034
- }
2035
-
2036
- getLanguageFromCode(code) {
2037
- return this.localeFromLangData(code).languages[code];
2038
- }
2039
-
2040
- getFieldNameFromPluginNameAndLocales({
2041
- pluginName,
2042
- locales,
2043
- lf,
2044
- targetLanguage,
2045
- applicableFieldI18N,
2046
- meta,
2047
- metaApplicableField
2048
- }) {
2049
- return lf(['plugins', pluginName, 'fieldname'], _objectSpread2(_objectSpread2(_objectSpread2({}, meta), metaApplicableField), {}, {
2050
- applicableField: applicableFieldI18N,
2051
- targetLanguage: targetLanguage ? this.getLanguageFromCode(targetLanguage) : ''
2052
- }));
2053
- }
2054
-
2055
- getLanguageInfo({
2056
- $p
2057
- }) {
2058
- const langs = this.langData.languages;
2059
-
2060
- const localePass = lcl => {
2061
- return langs.some(({
2062
- code
2063
- }) => code === lcl) ? lcl : false;
2064
- };
2065
-
2066
- const languageParam = $p.get('lang', true); // Todo: We could (unless overridden by another button) assume the
2067
- // browser language based on fallbackLanguages instead
2068
- // of giving a choice
2069
-
2070
- const navLangs = navigator.languages.filter(localePass);
2071
- const fallbackLanguages = navLangs.length ? navLangs : [localePass(navigator.language) || 'en-US']; // We need a default to display a default title
2072
-
2073
- const language = languageParam || fallbackLanguages[0];
2074
- const preferredLangs = language.split('.');
2075
- const lang = [...preferredLangs, ...fallbackLanguages];
2076
- return {
2077
- lang,
2078
- langs,
2079
- languageParam,
2080
- fallbackLanguages
2081
- };
2082
- }
2083
-
2084
- }
2085
-
2086
- /* eslint-env browser */
2087
-
2088
- const JsonRefs$1 = require('json-refs'); // eslint-disable-line import/order
2089
-
2090
-
2091
- const getCurrDir = () => window.location.href.replace(/(index\.html)?#.*$/, '');
2092
-
2093
- const getMetaProp = function getMetaProp(lang, metadataObj, properties, allowObjects) {
2094
- let prop;
2095
- properties = typeof properties === 'string' ? [properties] : properties;
2096
- lang.some(lan => {
2097
- const p = [...properties];
2098
- let strings = metadataObj['localization-strings'][lan];
2099
-
2100
- while (strings && p.length) {
2101
- strings = strings[p.shift()];
2102
- } // Todo: Fix this allowance for allowObjects (as it does not properly
2103
- // fallback if an object is returned from a language because
2104
- // that language is missing content and is only thus returning
2105
- // an object)
2106
-
2107
-
2108
- prop = allowObjects || typeof strings === 'string' ? strings : undefined;
2109
- return prop;
2110
- });
2111
- return prop;
2112
- }; // Use the following to dynamically add specific file schema in place of
2113
- // generic table schema if validating against files.jsonschema
2114
- // filesSchema.properties.groups.items.properties.files.items.properties.
2115
- // file.anyOf.splice(1, 1, {$ref: schemaFile});
2116
- // Todo: Allow use of dbs and fileGroup together in base directories?
2117
-
2118
- const getMetadata = async (file, property, basePath) => {
2119
- const url = new URL(basePath || getCurrDir());
2120
- url.search = ''; // Clear out query string, e.g., `?fbclid` from Facebook
2121
-
2122
- url.pathname = file;
2123
- url.hash = property ? '#/' + property : '';
2124
- return (await JsonRefs$1.resolveRefsAt(url.toString(), {
2125
- loaderOptions: {
2126
- processContent(res, callback) {
2127
- callback(undefined, JSON.parse(res.text || // `.metadata` not a recognized extension, so
2128
- // convert to string for JSON in Node
2129
- res.body.toString()));
2130
- }
2131
-
2132
- }
2133
- })).resolved;
2134
- };
2135
- const getFieldNameAndValueAliases = function ({
2136
- field,
2137
- schemaItems,
2138
- metadataObj,
2139
- getFieldAliasOrName,
2140
- lang
2141
- }) {
2142
- const fieldSchemaIndex = schemaItems.findIndex(item => item.title === field);
2143
- const fieldSchema = schemaItems[fieldSchemaIndex];
2144
- const ret = {
2145
- // field,
2146
- fieldName: getFieldAliasOrName(field)
2147
- };
2148
- const fieldInfo = metadataObj.fields[field];
2149
- let fieldValueAliasMap = fieldInfo && fieldInfo['fieldvalue-aliases'];
2150
-
2151
- if (fieldValueAliasMap) {
2152
- if (fieldValueAliasMap.localeKey) {
2153
- fieldValueAliasMap = getMetaProp(lang, metadataObj, fieldValueAliasMap.localeKey.split('/'), true);
2154
- }
2155
-
2156
- ret.rawFieldValueAliasMap = JSON.parse(JSON.stringify(fieldValueAliasMap));
2157
- ret.aliases = []; // Todo: We could use `prefer_alias` but algorithm below may cover
2158
- // needed cases
2159
-
2160
- if (fieldSchema.enum && fieldSchema.enum.length) {
2161
- fieldSchema.enum.forEach(enm => {
2162
- ret.aliases.push(getMetaProp(lang, metadataObj, ['fieldvalue', field, enm], true));
2163
-
2164
- if (enm in fieldValueAliasMap && // Todo: We could allow numbers here too, but crowds
2165
- // pull-down
2166
- typeof fieldValueAliasMap[enm] === 'string') {
2167
- ret.aliases.push(...fieldValueAliasMap[enm]);
2168
- }
2169
- });
2170
- } else {
2171
- // Todo: We might iterate over all values (in case some not
2172
- // included in fv map)
2173
- // Todo: Check `fieldSchema` for integer or string type
2174
- Object.entries(fieldValueAliasMap).forEach(([key, aliases]) => {
2175
- // We'll preserve the numbers since probably more useful if
2176
- // stored with data (as opposed to enums)
2177
- if (!Array.isArray(aliases)) {
2178
- aliases = Object.values(aliases);
2179
- } // We'll assume the longest version is best for auto-complete
2180
-
2181
-
2182
- ret.aliases.push(...aliases.filter(v => aliases.every(x => x === v || !x.toLowerCase().startsWith(v.toLowerCase()))).map(v => v + ' (' + key + ')') // Todo: i18nize
2183
- );
2184
- });
2185
- }
2186
-
2187
- ret.fieldValueAliasMap = JSON.parse(JSON.stringify(fieldValueAliasMap)); // ret.aliases.sort();
2188
- }
2189
-
2190
- ret.fieldSchema = fieldSchema;
2191
- ret.fieldSchemaIndex = fieldSchemaIndex;
2192
- ret.preferAlias = fieldInfo.prefer_alias;
2193
- ret.lang = fieldInfo.lang;
2194
- return ret;
2195
- };
2196
- const getBrowseFieldData = function ({
2197
- metadataObj,
2198
- schemaItems,
2199
- getFieldAliasOrName,
2200
- lang,
2201
- callback
2202
- }) {
2203
- metadataObj.table.browse_fields.forEach((browseFieldSetObject, i) => {
2204
- if (typeof browseFieldSetObject === 'string') {
2205
- browseFieldSetObject = {
2206
- set: [browseFieldSetObject]
2207
- };
2208
- }
2209
-
2210
- if (!browseFieldSetObject.name) {
2211
- browseFieldSetObject.name = browseFieldSetObject.set.join(',');
2212
- }
2213
-
2214
- const setName = browseFieldSetObject.name;
2215
- const fieldSets = browseFieldSetObject.set;
2216
- const {
2217
- presort
2218
- } = browseFieldSetObject; // Todo: Deal with ['td', [['h3', [ld(browseFieldObject.name)]]]]
2219
- // as kind of fieldset
2220
-
2221
- const browseFields = fieldSets.map(field => getFieldNameAndValueAliases({
2222
- lang,
2223
- field,
2224
- schemaItems,
2225
- metadataObj,
2226
- getFieldAliasOrName
2227
- }));
2228
- callback({
2229
- setName,
2230
- browseFields,
2231
- i,
2232
- presort
2233
- }); // eslint-disable-line node/no-callback-literal
2234
- });
2235
- }; // Todo: Incorporate other methods into this class
2236
-
2237
- class Metadata {
2238
- constructor({
2239
- metadataObj
2240
- }) {
2241
- this.metadataObj = metadataObj;
2242
- }
2243
-
2244
- getFieldLang(field) {
2245
- const {
2246
- metadataObj
2247
- } = this;
2248
- const fields = metadataObj && metadataObj.fields;
2249
- return fields && fields[field] && fields[field].lang;
2250
- }
2251
-
2252
- getFieldMatchesLocale({
2253
- namespace,
2254
- preferredLocale,
2255
- schemaItems,
2256
- pluginsForWork
2257
- }) {
2258
- const {
2259
- metadataObj
2260
- } = this;
2261
- return field => {
2262
- const preferredLanguages = getPreferredLanguages({
2263
- namespace,
2264
- preferredLocale
2265
- });
2266
-
2267
- if (pluginsForWork.isPluginField({
2268
- namespace,
2269
- field
2270
- })) {
2271
- let [,, targetLanguage] = pluginsForWork.getPluginFieldParts({
2272
- namespace,
2273
- field
2274
- });
2275
-
2276
- if (targetLanguage === '{locale}') {
2277
- targetLanguage = preferredLocale;
2278
- }
2279
-
2280
- return !targetLanguage || preferredLanguages.includes(targetLanguage);
2281
- }
2282
-
2283
- const metaLang = this.getFieldLang(field);
2284
- const localeStrings = metadataObj && metadataObj['localization-strings']; // If this is a localized field (e.g., enum), we don't want
2285
- // to avoid as may be translated (should check though)
2286
-
2287
- const hasFieldValue = localeStrings && Object.keys(localeStrings).some(lng => {
2288
- const fv = localeStrings[lng] && localeStrings[lng].fieldvalue;
2289
- return fv && fv[field];
2290
- });
2291
- return hasFieldValue || metaLang && preferredLanguages.includes(metaLang) || schemaItems.some(item => item.title === field && item.type !== 'string');
2292
- };
2293
- }
2294
-
2295
- }
2296
-
2297
- const escapePluginComponent = pluginName => {
2298
- return pluginName.replace(/\^/g, '^^') // Escape our escape
2299
- .replace(/-/g, '^0');
2300
- };
2301
- const unescapePluginComponent = pluginName => {
2302
- if (!pluginName) {
2303
- return pluginName;
2304
- }
2305
-
2306
- return pluginName.replace(/(\^+)0/g, (n0, esc) => {
2307
- return esc.length % 2 ? esc.slice(1) + '-' : n0;
2308
- }).replace(/\^\^/g, '^');
2309
- };
2310
- const escapePlugin = ({
2311
- pluginName,
2312
- applicableField,
2313
- targetLanguage
2314
- }) => {
2315
- return escapePluginComponent(pluginName) + (applicableField ? '-' + escapePluginComponent(applicableField) : '-') + (targetLanguage ? '-' + escapePluginComponent(targetLanguage) : '');
2316
- };
2317
- class PluginsForWork {
2318
- constructor({
2319
- pluginsInWork,
2320
- pluginFieldMappings,
2321
- pluginObjects
2322
- }) {
2323
- this.pluginsInWork = pluginsInWork;
2324
- this.pluginFieldMappings = pluginFieldMappings;
2325
- this.pluginObjects = pluginObjects;
2326
- }
2327
-
2328
- getPluginObject(pluginName) {
2329
- const idx = this.pluginsInWork.findIndex(([name]) => {
2330
- return name === pluginName;
2331
- });
2332
- const plugin = this.pluginObjects[idx];
2333
- return plugin;
2334
- }
2335
-
2336
- iterateMappings(cb) {
2337
- this.pluginFieldMappings.forEach(({
2338
- placement,
2339
-
2340
- /*
2341
- {fieldXYZ: {
2342
- targetLanguage: "en"|["en"], // E.g., translating from Persian to English
2343
- onByDefault: true // Overrides plugin default
2344
- }}
2345
- */
2346
- 'applicable-fields': applicableFields
2347
- }, i) => {
2348
- const [pluginName, {
2349
- onByDefault: onByDefaultDefault,
2350
- lang: pluginLang,
2351
- meta
2352
- }] = this.pluginsInWork[i];
2353
- const plugin = this.getPluginObject(pluginName);
2354
- cb({
2355
- // eslint-disable-line node/no-callback-literal
2356
- plugin,
2357
- placement,
2358
- applicableFields,
2359
- pluginName,
2360
- pluginLang,
2361
- onByDefaultDefault,
2362
- meta
2363
- });
2364
- });
2365
- }
2366
-
2367
- processTargetLanguages(applicableFields, cb) {
2368
- if (!applicableFields) {
2369
- return false;
2370
- }
2371
-
2372
- Object.entries(applicableFields).forEach(([applicableField, {
2373
- targetLanguage,
2374
- onByDefault,
2375
- meta: metaApplicableField
2376
- }]) => {
2377
- if (Array.isArray(targetLanguage)) {
2378
- targetLanguage.forEach(targetLanguage => {
2379
- cb({
2380
- applicableField,
2381
- targetLanguage,
2382
- onByDefault,
2383
- metaApplicableField
2384
- }); // eslint-disable-line node/no-callback-literal
2385
- });
2386
- } else {
2387
- // eslint-disable-next-line node/callback-return
2388
- cb({
2389
- applicableField,
2390
- targetLanguage,
2391
- onByDefault,
2392
- metaApplicableField
2393
- }); // eslint-disable-line node/no-callback-literal
2394
- }
2395
- });
2396
- return true;
2397
- }
2398
-
2399
- isPluginField({
2400
- namespace,
2401
- field
2402
- }) {
2403
- return field.startsWith(`${namespace}-plugin-`);
2404
- }
2405
-
2406
- getPluginFieldParts({
2407
- namespace,
2408
- field
2409
- }) {
2410
- field = field.replace(`${namespace}-plugin-`, '');
2411
- let pluginName, applicableField, targetLanguage;
2412
-
2413
- if (field.includes('-')) {
2414
- [pluginName, applicableField, targetLanguage] = field.split('-');
2415
- } else {
2416
- pluginName = field;
2417
- }
2418
-
2419
- return [pluginName, applicableField, targetLanguage].map(unescapePluginComponent);
2420
- }
2421
-
2422
- }
2423
-
2424
- let path, babelRegister;
2425
-
2426
- if (typeof process !== 'undefined') {
2427
- /* eslint-disable node/global-require */
2428
- path = require('path');
2429
- babelRegister = require('@babel/register');
2430
- /* eslint-enable node/global-require */
2431
- }
2432
- const getFilePaths = function getFilePaths(filesObj, fileGroup, fileData) {
2433
- const baseDir = (filesObj.baseDirectory || '') + (fileGroup.baseDirectory || '') + '/';
2434
- const schemaBaseDir = (filesObj.schemaBaseDirectory || '') + (fileGroup.schemaBaseDirectory || '') + '/';
2435
- const metadataBaseDir = (filesObj.metadataBaseDirectory || '') + (fileGroup.metadataBaseDirectory || '') + '/';
2436
- const file = baseDir + fileData.file.$ref;
2437
- const schemaFile = fileData.schemaFile ? schemaBaseDir + fileData.schemaFile : '';
2438
- const metadataFile = fileData.metadataFile ? metadataBaseDir + fileData.metadataFile : '';
2439
- return {
2440
- file,
2441
- schemaFile,
2442
- metadataFile
2443
- };
2444
- };
2445
- const getWorkData = async function ({
2446
- lang,
2447
- fallbackLanguages,
2448
- work,
2449
- files,
2450
- allowPlugins,
2451
- basePath,
2452
- languages,
2453
- preferredLocale
2454
- }) {
2455
- const filesObj = await simpleGetJson.getJSON(files);
2456
-
2457
- const localeFromFileData = lan => filesObj['localization-strings'][lan];
2458
-
2459
- const imfFile = imf.IMF({
2460
- locales: lang.map(localeFromFileData),
2461
- fallbackLocales: fallbackLanguages.map(localeFromFileData)
2462
- });
2463
- const lf = imfFile.getFormatter();
2464
- let fileData;
2465
- const fileGroup = filesObj.groups.find(fg => {
2466
- fileData = fg.files.find(file => work === lf(['workNames', fg.id, file.name]));
2467
- return Boolean(fileData);
2468
- }); // This is not specific to the work, but we export it anyways
2469
-
2470
- const groupsToWorks = filesObj.groups.map(fg => {
2471
- return {
2472
- name: lf({
2473
- key: fg.name.localeKey,
2474
- fallback: true
2475
- }),
2476
- workNames: fg.files.map(file => {
2477
- return lf(['workNames', fg.id, file.name]);
2478
- }),
2479
- shortcuts: fg.files.map(file => file.shortcut)
2480
- };
2481
- });
2482
- const fp = getFilePaths(filesObj, fileGroup, fileData);
2483
- const {
2484
- file
2485
- } = fp;
2486
- let {
2487
- schemaFile,
2488
- metadataFile
2489
- } = fp;
2490
- let schemaProperty = '',
2491
- metadataProperty = '';
2492
-
2493
- if (!schemaFile) {
2494
- schemaFile = file;
2495
- schemaProperty = 'schema';
2496
- }
2497
-
2498
- if (!metadataFile) {
2499
- metadataFile = file;
2500
- metadataProperty = 'metadata';
2501
- }
2502
-
2503
- let getPlugins,
2504
- pluginsInWork,
2505
- pluginFieldsForWork,
2506
- pluginPaths,
2507
- pluginFieldMappingForWork = [];
2508
-
2509
- if (allowPlugins) {
2510
- const pluginFieldMapping = filesObj['plugin-field-mapping'];
2511
- const pluginFieldMappingID = pluginFieldMapping[fileGroup.id];
2512
- const possiblePluginFieldMappingForWork = pluginFieldMappingID[fileData.name];
2513
-
2514
- if (possiblePluginFieldMappingForWork) {
2515
- pluginFieldsForWork = Object.keys(possiblePluginFieldMappingForWork);
2516
- pluginsInWork = Object.entries(filesObj.plugins).filter(([p]) => {
2517
- return pluginFieldsForWork.includes(p);
2518
- });
2519
- pluginFieldMappingForWork = pluginsInWork.map(([p]) => {
2520
- return possiblePluginFieldMappingForWork[p];
2521
- });
2522
- pluginPaths = pluginsInWork.map(([, pluginObj]) => pluginObj.path);
2523
- getPlugins = pluginsInWork;
2524
- }
2525
- }
2526
-
2527
- const metadataObj = await getMetadata(metadataFile, metadataProperty, basePath);
2528
-
2529
- const getFieldAliasOrName = function getFieldAliasOrName(field) {
2530
- const fieldObj = metadataObj.fields && metadataObj.fields[field];
2531
- let fieldName;
2532
- let fieldAlias;
2533
-
2534
- if (fieldObj) {
2535
- fieldAlias = fieldObj.alias;
2536
- }
2537
-
2538
- if (fieldAlias) {
2539
- if (typeof fieldAlias === 'string') {
2540
- fieldName = fieldAlias;
2541
- } else {
2542
- fieldAlias = fieldAlias.localeKey;
2543
- fieldName = getMetaProp(lang, metadataObj, fieldAlias.split('/'));
2544
- }
2545
- } else {
2546
- // No alias
2547
- fieldName = fieldObj.name;
2548
-
2549
- if (typeof fieldName === 'object') {
2550
- fieldName = fieldName.localeKey;
2551
- fieldName = getMetaProp(lang, metadataObj, fieldName.split('/'));
2552
- }
2553
- }
2554
-
2555
- return fieldName;
2556
- };
2557
-
2558
- const pluginFieldMappings = pluginFieldMappingForWork;
2559
- const [schemaObj, pluginObjects] = await Promise.all([getMetadata(schemaFile, schemaProperty, basePath), getPlugins ? Promise.all(pluginPaths.map(pluginPath => {
2560
- if (typeof process !== 'undefined') {
2561
- pluginPath = path.resolve(path.join(process.cwd(), 'node_modules/textbrowser/server', pluginPath));
2562
- babelRegister({
2563
- presets: ['@babel/env']
2564
- });
2565
- return Promise.resolve().then(() => {
2566
- return require(pluginPath); // eslint-disable-line node/global-require, import/no-dynamic-require
2567
- }).catch(err => {
2568
- // E.g., with tooltips plugin
2569
- console.log('err', err);
2570
- });
2571
- } // eslint-disable-next-line no-unsanitized/method
2572
-
2573
-
2574
- return Promise.resolve(`${pluginPath}`).then(s => _interopRequireWildcard(require(s)));
2575
- })) : null]);
2576
- const pluginsForWork = new PluginsForWork({
2577
- pluginsInWork,
2578
- pluginFieldMappings,
2579
- pluginObjects
2580
- });
2581
- const schemaItems = schemaObj.items.items;
2582
- const fieldInfo = schemaItems.map(({
2583
- title: field
2584
- }) => {
2585
- return {
2586
- field,
2587
- fieldAliasOrName: getFieldAliasOrName(field) || field
2588
- };
2589
- });
2590
- const metadata = new Metadata({
2591
- metadataObj
2592
- });
2593
-
2594
- if (languages && // Avoid all this processing if this is not the specific call requiring
2595
- pluginsForWork) {
2596
- console.log('pluginsForWork', pluginsForWork);
2597
- const {
2598
- lang,
2599
- namespace
2600
- } = this; // array with first item as preferred
2601
-
2602
- pluginsForWork.iterateMappings(({
2603
- plugin,
2604
- pluginName,
2605
- pluginLang,
2606
- onByDefaultDefault,
2607
- placement,
2608
- applicableFields,
2609
- meta
2610
- }) => {
2611
- const processField = ({
2612
- applicableField,
2613
- targetLanguage,
2614
- onByDefault,
2615
- metaApplicableField
2616
- } = {}) => {
2617
- const plugin = pluginsForWork.getPluginObject(pluginName) || {};
2618
- const applicableFieldLang = metadata.getFieldLang(applicableField);
2619
-
2620
- if (plugin.getTargetLanguage) {
2621
- targetLanguage = plugin.getTargetLanguage({
2622
- applicableField,
2623
- targetLanguage,
2624
- // Default lang for plug-in (from files.json)
2625
- pluginLang,
2626
- // Default lang when no target language or
2627
- // plugin lang; using the lang of the applicable
2628
- // field
2629
- applicableFieldLang
2630
- });
2631
- }
2632
-
2633
- const field = escapePlugin({
2634
- pluginName,
2635
- applicableField,
2636
- targetLanguage: targetLanguage || pluginLang || applicableFieldLang
2637
- });
2638
-
2639
- if (targetLanguage === '{locale}') {
2640
- targetLanguage = preferredLocale;
2641
- }
2642
-
2643
- const applicableFieldI18N = getMetaProp(lang, metadataObj, ['fieldnames', applicableField]);
2644
- const fieldAliasOrName = plugin.getFieldAliasOrName ? plugin.getFieldAliasOrName({
2645
- locales: lang,
2646
- lf,
2647
- targetLanguage,
2648
- applicableField,
2649
- applicableFieldI18N,
2650
- meta,
2651
- metaApplicableField,
2652
- targetLanguageI18N: languages.getLanguageFromCode(targetLanguage)
2653
- }) : languages.getFieldNameFromPluginNameAndLocales({
2654
- pluginName,
2655
- locales: lang,
2656
- lf,
2657
- targetLanguage,
2658
- applicableFieldI18N,
2659
- // Todo: Should have formal way to i18nize meta
2660
- meta,
2661
- metaApplicableField
2662
- });
2663
- fieldInfo.splice( // Todo: Allow default placement overriding for
2664
- // non-plugins
2665
- placement === 'end' ? Number.POSITIVE_INFINITY // push
2666
- : placement, 0, {
2667
- field: `${namespace}-plugin-${field}`,
2668
- fieldAliasOrName,
2669
- // Plug-in specific (todo: allow specifying
2670
- // for non-plugins)
2671
- onByDefault: typeof onByDefault === 'boolean' ? onByDefault : onByDefaultDefault || false,
2672
- // Three conventions for use by plug-ins but
2673
- // textbrowser only passes on (might
2674
- // not need here)
2675
- applicableField,
2676
- metaApplicableField,
2677
- fieldLang: targetLanguage
2678
- });
2679
- };
2680
-
2681
- if (!pluginsForWork.processTargetLanguages(applicableFields, processField)) {
2682
- processField();
2683
- }
2684
- });
2685
- }
2686
-
2687
- return {
2688
- fileData,
2689
- lf,
2690
- getFieldAliasOrName,
2691
- metadataObj,
2692
- schemaObj,
2693
- schemaItems,
2694
- fieldInfo,
2695
- pluginsForWork,
2696
- groupsToWorks,
2697
- metadata
2698
- };
2699
- };
2700
-
2701
- const JsonRefs = require('json-refs');
2702
-
2703
- const fieldValueAliasRegex = /^.* \((.*?)\)$/;
2704
-
2705
- const getRawFieldValue = v => {
2706
- return typeof v === 'string' ? v.replace(fieldValueAliasRegex, '$1') : v;
2707
- };
2708
- const resultsDisplayServer = async function resultsDisplayServer(args) {
2709
- const {
2710
- templateArgs
2711
- } = await resultsDisplayServerOrClient.call(this, _objectSpread2({}, args)); // Todo: Should really reconcile this with client-side output options
2712
- // (as should also have option there to get JSON, Jamilih, etc.
2713
- // output)
2714
-
2715
- switch (args.serverOutput) {
2716
- case 'jamilih':
2717
- return Templates.resultsDisplayServerOrClient.main(templateArgs);
2718
-
2719
- case 'html':
2720
- {
2721
- const jamilih$1 = Templates.resultsDisplayServerOrClient.main(templateArgs);
2722
- return jamilih.jml.toHTML(...jamilih$1);
2723
- }
2724
-
2725
- case 'json':
2726
- default:
2727
- return templateArgs.tableData;
2728
- }
2729
- };
2730
- const resultsDisplayServerOrClient = async function resultsDisplayServerOrClient({
2731
- l,
2732
- lang,
2733
- fallbackLanguages,
2734
- imfLocales,
2735
- $p,
2736
- skipIndexedDB,
2737
- noIndexedDB,
2738
- prefI18n,
2739
- files,
2740
- allowPlugins,
2741
- langData,
2742
- basePath = '',
2743
- dynamicBasePath = ''
2744
- }) {
2745
- const languages = new Languages({
2746
- langData
2747
- });
2748
-
2749
- const getCellValue = ({
2750
- fieldValueAliasMapPreferred,
2751
- escapeColumnIndexes
2752
- }) => ({
2753
- tr,
2754
- idx
2755
- }) => {
2756
- let tdVal = fieldValueAliasMapPreferred[idx] !== undefined ? fieldValueAliasMapPreferred[idx][tr[idx]] : tr[idx];
2757
-
2758
- if (tdVal && typeof tdVal === 'object') {
2759
- tdVal = Object.values(tdVal);
2760
- }
2761
-
2762
- if (Array.isArray(tdVal)) {
2763
- tdVal = tdVal.join(l('comma-space'));
2764
- }
2765
-
2766
- return (escapeColumnIndexes[idx] || !this.trustFormatHTML) && typeof tdVal === 'string' ? {
2767
- tdVal: escapeHTML(tdVal),
2768
- htmlEscaped: true
2769
- } : {
2770
- tdVal
2771
- };
2772
- };
2773
-
2774
- const getCanonicalID = ({
2775
- fieldValueAliasMap,
2776
- fieldValueAliasMapPreferred,
2777
- localizedFieldNames,
2778
- canonicalBrowseFieldNames
2779
- }) => ({
2780
- tr,
2781
- foundState
2782
- }) => {
2783
- return canonicalBrowseFieldNames.map(fieldName => {
2784
- const idx = localizedFieldNames.indexOf(fieldName); // This works to put alias in anchor but this includes
2785
- // our ending parenthetical, the alias may be harder
2786
- // to remember and/or automated than original (e.g.,
2787
- // for a number representing a book); we may wish to
2788
- // switch this (and also for other browse field-based
2789
- // items).
2790
-
2791
- if (fieldValueAliasMap[idx] !== undefined) {
2792
- return fieldValueAliasMapPreferred[idx][tr[idx]];
2793
- }
2794
-
2795
- return tr[idx];
2796
- }).join('-'); // rowID;
2797
- };
2798
-
2799
- const determineEnd = ({
2800
- fieldValueAliasMap,
2801
- fieldValueAliasMapPreferred,
2802
- localizedFieldNames,
2803
- applicableBrowseFieldNames,
2804
- startsRaw,
2805
- endsRaw
2806
- }) => ({
2807
- tr,
2808
- foundState
2809
- }) => {
2810
- const rowIDPartsPreferred = [];
2811
- const rowIDParts = applicableBrowseFieldNames.map(fieldName => {
2812
- const idx = localizedFieldNames.indexOf(fieldName); // This works to put alias in anchor but this includes
2813
- // our ending parenthetical, the alias may be harder
2814
- // to remember and/or automated than original (e.g.,
2815
- // for a number representing a book), and there could
2816
- // be multiple aliases for a value; we may wish to
2817
- // switch this (and also for other browse field-based
2818
- // items).
2819
-
2820
- if (fieldValueAliasMap[idx] !== undefined) {
2821
- rowIDPartsPreferred.push(fieldValueAliasMapPreferred[idx][tr[idx]]);
2822
- } else {
2823
- rowIDPartsPreferred.push(tr[idx]);
2824
- }
2825
-
2826
- return tr[idx];
2827
- }); // Todo: Use schema to determine field type and use `Number.parseInt`
2828
- // on other value instead of `String` conversions
2829
-
2830
- if (!foundState.start) {
2831
- if (startsRaw.some((part, i) => {
2832
- const rowIDPart = rowIDParts[i];
2833
- return part !== rowIDPart;
2834
- })) {
2835
- // Trigger skip of this row
2836
- return false;
2837
- }
2838
-
2839
- foundState.start = true;
2840
- } // This doesn't go in an `else` for the above in case the start is the end
2841
-
2842
-
2843
- if (endsRaw.every((part, i) => {
2844
- const rowIDPart = rowIDParts[i];
2845
- return part === rowIDPart;
2846
- })) {
2847
- foundState.end = true;
2848
- } else if (foundState.end) {
2849
- // If no longer matching, trigger end of the table
2850
- return true;
2851
- }
2852
-
2853
- return rowIDPartsPreferred.join('-'); // rowID;
2854
- };
2855
-
2856
- const getCheckedAndInterlinearFieldInfo = ({
2857
- localizedFieldNames
2858
- }) => {
2859
- let i = 1;
2860
- let field, checked;
2861
- let checkedFields = [];
2862
-
2863
- do {
2864
- field = $p.get('field' + i, true);
2865
- checked = $p.get('checked' + i, true);
2866
- i++;
2867
-
2868
- if (field && (checked === l('yes') || checked === null // Default to "on"
2869
- )) {
2870
- checkedFields.push(field);
2871
- }
2872
- } while (field);
2873
-
2874
- checkedFields = checkedFields.filter(cf => localizedFieldNames.includes(cf));
2875
- const checkedFieldIndexes = checkedFields.map(cf => localizedFieldNames.indexOf(cf));
2876
- const allInterlinearColIndexes = checkedFieldIndexes.map((cfi, i) => {
2877
- const interlin = $p.get('interlin' + (cfi + 1), true);
2878
- return interlin && interlin.split(/\s*,\s*/).map(col => // Todo: Avoid this when known to be integer or if string, though allow
2879
- // string to be treated as number if config is set.
2880
- Number.parseInt(col) - 1).filter(n => !Number.isNaN(n));
2881
- });
2882
- return [checkedFields, checkedFieldIndexes, allInterlinearColIndexes];
2883
- };
2884
-
2885
- const getCaption = ({
2886
- starts,
2887
- ends,
2888
- applicableBrowseFieldNames,
2889
- heading
2890
- }) => {
2891
- let caption;
2892
- const hasCaption = $pRaw('caption') !== '0';
2893
-
2894
- if (hasCaption) {
2895
- /*
2896
- // Works but displays in parentheses browse fields which
2897
- // may be non-applicable
2898
- const buildRangePoint = (startOrEnd) => escapeHTML(
2899
- browseFieldSets.reduce((txt, bfs, i) =>
2900
- (txt ? txt + ' (' : '') + bfs.map((bf, j) =>
2901
- (j > 0 ? l('comma-space') : '') + bf + ' ' +
2902
- $pRaw(startOrEnd + (i + 1) + '-' + (j + 1))
2903
- ).join('') + (txt ? ')' : ''), '')
2904
- );
2905
- */
2906
-
2907
- /*
2908
- // Works but overly long
2909
- const buildRangePoint = (startOrEnd) => escapeHTML(
2910
- applicableBrowseFieldSet.map((bf, j) =>
2911
- (j > 0 ? l('comma-space') : '') + bf + ' ' +
2912
- $pRaw(startOrEnd + (browseFieldSetIdx + 1) + '-' + (j + 1))
2913
- ).join('')
2914
- );
2915
- */
2916
- const startSep = Templates.resultsDisplayServerOrClient.startSeparator({
2917
- l
2918
- });
2919
- const innerBrowseFieldSeparator = Templates.resultsDisplayServerOrClient.innerBrowseFieldSeparator({
2920
- l
2921
- });
2922
-
2923
- const buildRanges = () => {
2924
- const endVals = [];
2925
- const startRange = starts.reduce((str, startFieldValue, i) => {
2926
- const ret = str + startFieldValue;
2927
-
2928
- if (startFieldValue === ends[i]) {
2929
- // We abbreviate as start/end share same Book, etc.
2930
- return ret + (i > 0 ? innerBrowseFieldSeparator // e.g., for "Genesis 7, 5-8"
2931
- : ' ' // e.g., for 2nd space in "Surah 2 5-8"
2932
- );
2933
- }
2934
-
2935
- endVals.push(ends[i]);
2936
- return ret + startSep;
2937
- }, '').slice(0, -startSep.length);
2938
- const rangeNames = applicableBrowseFieldNames.join(innerBrowseFieldSeparator);
2939
- return escapeHTML(Templates.resultsDisplayServerOrClient.ranges({
2940
- l,
2941
- startRange,
2942
- endVals,
2943
- rangeNames
2944
- }));
2945
- };
2946
-
2947
- const ranges = buildRanges();
2948
- caption = Templates.resultsDisplayServerOrClient.caption({
2949
- heading,
2950
- ranges
2951
- });
2952
- }
2953
-
2954
- return [hasCaption, caption];
2955
- };
2956
-
2957
- const runPresort = ({
2958
- presort,
2959
- tableData,
2960
- applicableBrowseFieldNames,
2961
- localizedFieldNames
2962
- }) => {
2963
- // Todo: Ought to be checking against an aliased table
2964
- if (presort) {
2965
- tableData.sort((rowA, rowB) => {
2966
- let precedence;
2967
- applicableBrowseFieldNames.some(fieldName => {
2968
- const idx = localizedFieldNames.indexOf(fieldName);
2969
- const rowAFirst = rowA[idx] < rowB[idx];
2970
- const rowBFirst = rowA[idx] > rowB[idx];
2971
- precedence = rowBFirst ? 1 : -1;
2972
- return rowAFirst || rowBFirst; // Keep going if 0
2973
- });
2974
- return precedence;
2975
- });
2976
- }
2977
- };
2978
-
2979
- const getFieldValueAliasMap = ({
2980
- schemaItems,
2981
- fieldInfo,
2982
- metadataObj,
2983
- getFieldAliasOrName,
2984
- usePreferAlias
2985
- }) => {
2986
- return fieldInfo.map(({
2987
- field,
2988
- plugin
2989
- }) => {
2990
- if (plugin) {
2991
- return undefined;
2992
- }
2993
-
2994
- const {
2995
- preferAlias,
2996
- fieldValueAliasMap
2997
- } = getFieldNameAndValueAliases({
2998
- field,
2999
- schemaItems,
3000
- metadataObj,
3001
- getFieldAliasOrName,
3002
- lang
3003
- });
3004
-
3005
- if (!usePreferAlias) {
3006
- return preferAlias !== false ? fieldValueAliasMap : undefined;
3007
- }
3008
-
3009
- if (fieldValueAliasMap) {
3010
- Object.entries(fieldValueAliasMap).forEach(([key, val]) => {
3011
- if (Array.isArray(val)) {
3012
- fieldValueAliasMap[key] = val.map(value => Templates.resultsDisplayServerOrClient.fieldValueAlias({
3013
- key,
3014
- value
3015
- }));
3016
- return;
3017
- }
3018
-
3019
- if (val && typeof val === 'object') {
3020
- if (typeof preferAlias === 'string') {
3021
- fieldValueAliasMap[key] = Templates.resultsDisplayServerOrClient.fieldValueAlias({
3022
- key,
3023
- value: val[preferAlias]
3024
- });
3025
- } else {
3026
- Object.entries(val).forEach(([k, value]) => {
3027
- fieldValueAliasMap[key][k] = Templates.resultsDisplayServerOrClient.fieldValueAlias({
3028
- key,
3029
- value
3030
- });
3031
- });
3032
- }
3033
-
3034
- return;
3035
- }
3036
-
3037
- fieldValueAliasMap[key] = Templates.resultsDisplayServerOrClient.fieldValueAlias({
3038
- key,
3039
- value: val
3040
- });
3041
- });
3042
- return preferAlias !== false ? fieldValueAliasMap : undefined;
3043
- }
3044
-
3045
- return undefined;
3046
- });
3047
- };
3048
-
3049
- const $pRaw = (param, avoidLog) => {
3050
- // Todo: Should work with i18n=true (if names i18nized, need reverse look-up)
3051
- let key;
3052
- const p = $p.get(param, true);
3053
- /**
3054
- *
3055
- * @param {GenericArray|PlainObject} locale
3056
- * @returns {boolean}
3057
- */
3058
-
3059
- function reverseLocaleLookup(locale) {
3060
- if (Array.isArray(locale)) {
3061
- return locale.some(reverseLocaleLookup);
3062
- }
3063
-
3064
- const localeValues = Object.values(locale);
3065
- return localeValues.some((val, i) => {
3066
- if (typeof val !== 'string') {
3067
- return reverseLocaleLookup(val);
3068
- }
3069
-
3070
- if (val === p) {
3071
- key = Object.keys(locale)[i];
3072
- return true;
3073
- }
3074
-
3075
- return false;
3076
- });
3077
- }
3078
-
3079
- reverseLocaleLookup(imfLocales);
3080
-
3081
- if (!key && !avoidLog) {
3082
- console.log('Bad param/value', param, '::', p);
3083
- }
3084
-
3085
- return key; // || p; // $p.get(param, true);
3086
- };
3087
-
3088
- const escapeCSS = escapeHTML;
3089
-
3090
- const $pRawEsc = param => escapeHTML($pRaw(param));
3091
-
3092
- const $pEscArbitrary = param => escapeHTML($p.get(param, true)); // Not currently in use
3093
-
3094
-
3095
- const escapeQuotedCSS = s => s.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
3096
-
3097
- const {
3098
- fileData,
3099
- lf,
3100
- getFieldAliasOrName,
3101
- schemaObj,
3102
- metadataObj,
3103
- pluginsForWork
3104
- } = await getWorkData({
3105
- files: files || this.files,
3106
- allowPlugins: typeof allowPlugins === 'boolean' ? allowPlugins : this.allowPlugins,
3107
- lang,
3108
- fallbackLanguages,
3109
- work: $p.get('work'),
3110
- basePath
3111
- });
3112
- console.log('pluginsForWork', pluginsForWork);
3113
- const heading = getMetaProp(lang, metadataObj, 'heading');
3114
- const schemaItems = schemaObj.items.items;
3115
- const setNames = [];
3116
- const presorts = [];
3117
- const browseFieldSets = [];
3118
- getBrowseFieldData({
3119
- metadataObj,
3120
- schemaItems,
3121
- getFieldAliasOrName,
3122
- lang,
3123
-
3124
- callback({
3125
- setName,
3126
- browseFields,
3127
- presort
3128
- }) {
3129
- setNames.push(setName);
3130
- presorts.push(presort);
3131
- browseFieldSets.push(browseFields);
3132
- }
3133
-
3134
- });
3135
- const fieldInfo = schemaItems.map(({
3136
- title: field,
3137
- format
3138
- }) => {
3139
- return {
3140
- field,
3141
- fieldAliasOrName: getFieldAliasOrName(field) || field,
3142
- escapeColumn: format !== 'html',
3143
- fieldLang: metadataObj.fields[field].lang
3144
- };
3145
- });
3146
- const [preferredLocale] = lang;
3147
- const metadata = new Metadata({
3148
- metadataObj
3149
- });
3150
-
3151
- if (pluginsForWork) {
3152
- console.log('pluginsForWork', pluginsForWork);
3153
- const {
3154
- lang
3155
- } = this; // array with first item as preferred
3156
-
3157
- pluginsForWork.iterateMappings(({
3158
- plugin,
3159
- pluginName,
3160
- pluginLang,
3161
- onByDefaultDefault,
3162
- placement,
3163
- applicableFields,
3164
- meta
3165
- }) => {
3166
- placement = placement === 'end' ? Number.POSITIVE_INFINITY // push
3167
- : placement;
3168
-
3169
- const processField = ({
3170
- applicableField,
3171
- targetLanguage,
3172
- onByDefault,
3173
- metaApplicableField
3174
- } = {}) => {
3175
- const plugin = pluginsForWork.getPluginObject(pluginName) || {};
3176
- const applicableFieldLang = metadata.getFieldLang(applicableField);
3177
-
3178
- if (plugin.getTargetLanguage) {
3179
- targetLanguage = plugin.getTargetLanguage({
3180
- applicableField,
3181
- targetLanguage,
3182
- // Default lang for plug-in (from files.json)
3183
- pluginLang,
3184
- // Default lang when no target language or
3185
- // plugin lang; using the lang of the applicable
3186
- // field
3187
- applicableFieldLang
3188
- });
3189
- }
3190
-
3191
- if (targetLanguage === '{locale}') {
3192
- targetLanguage = preferredLocale;
3193
- }
3194
-
3195
- const applicableFieldI18N = getMetaProp(lang, metadataObj, ['fieldnames', applicableField]);
3196
- const fieldAliasOrName = plugin.getFieldAliasOrName ? plugin.getFieldAliasOrName({
3197
- locales: lang,
3198
- lf,
3199
- targetLanguage,
3200
- applicableField,
3201
- applicableFieldI18N,
3202
- meta,
3203
- metaApplicableField,
3204
- targetLanguageI18N: languages.getLanguageFromCode(targetLanguage)
3205
- }) : languages.getFieldNameFromPluginNameAndLocales({
3206
- pluginName,
3207
- locales: lang,
3208
- lf,
3209
- targetLanguage,
3210
- applicableFieldI18N,
3211
- // Todo: Should have formal way to i18nize meta
3212
- meta,
3213
- metaApplicableField
3214
- });
3215
- fieldInfo.splice( // Todo: Allow default placement overriding for
3216
- // non-plugins
3217
- placement, 0, {
3218
- plugin,
3219
- meta,
3220
- placement,
3221
- // field: `${this.namespace}-plugin-${field}`,
3222
- fieldAliasOrName,
3223
- escapeColumn: plugin.escapeColumn !== false,
3224
- // Plug-in specific (todo: allow specifying
3225
- // for non-plugins)
3226
- onByDefault: typeof onByDefault === 'boolean' ? onByDefault : onByDefaultDefault || false,
3227
- // Three conventions for use by plug-ins but
3228
- // textbrowser only passes on (might
3229
- // not need here)
3230
- applicableField,
3231
- metaApplicableField,
3232
- fieldLang: targetLanguage
3233
- });
3234
- };
3235
-
3236
- if (!pluginsForWork.processTargetLanguages(applicableFields, processField)) {
3237
- processField();
3238
- }
3239
- });
3240
- }
3241
-
3242
- const localizedFieldNames = fieldInfo.map(fi => fi.fieldAliasOrName);
3243
- const escapeColumnIndexes = fieldInfo.map(fi => fi.escapeColumn);
3244
- const fieldLangs = fieldInfo.map(({
3245
- fieldLang
3246
- }) => {
3247
- return fieldLang !== preferredLocale ? fieldLang : null;
3248
- });
3249
- const fieldValueAliasMap = getFieldValueAliasMap({
3250
- schemaItems,
3251
- fieldInfo,
3252
- metadataObj,
3253
- getFieldAliasOrName,
3254
- usePreferAlias: false
3255
- });
3256
- const fieldValueAliasMapPreferred = getFieldValueAliasMap({
3257
- schemaItems,
3258
- fieldInfo,
3259
- metadataObj,
3260
- getFieldAliasOrName,
3261
- usePreferAlias: true
3262
- }); // Todo: Repeats some code in workDisplay; probably need to reuse
3263
- // these functions more in `Templates.resultsDisplayServerOrClient` too
3264
-
3265
- const localizeParamNames = $p.localizeParamNames = $p.has('i18n', true) ? $p.get('i18n', true) === '1' : prefI18n === 'true' || prefI18n !== 'false' && this.localizeParamNames;
3266
- const il = localizeParamNames ? key => l(['params', key]) : key => key;
3267
- const iil = localizeParamNames ? key => l(['params', 'indexed', key]) : key => key;
3268
- const ilRaw = localizeParamNames ? (key, suffix = '') => $p.get(il(key) + suffix, true) : (key, suffix = '') => $p.get(key + suffix, true);
3269
- const iilRaw = localizeParamNames ? (key, suffix = '') => $p.get($p.get('work') + '-' + iil(key) + suffix, true) : (key, suffix = '') => $p.get($p.get('work') + '-' + key + suffix, true); // Now that we know `browseFieldSets`, we can parse `startEnd`
3270
-
3271
- const browseFieldSetStartEndIdx = browseFieldSets.findIndex((item, i) => iilRaw('startEnd', i + 1));
3272
-
3273
- if (browseFieldSetStartEndIdx !== -1) {
3274
- // Todo: i18nize (by work and/or by whole app?)
3275
- const rangeSep = '-';
3276
- const partSep = ':'; // Search box functionality (Todo: not yet in UI); should first
3277
- // avoid numeric startEnd and even work across book
3278
- // Todo: At least avoid need for book text AND book number in Bible
3279
- // Todo: Change query beginning at 0 to 1 if none present?
3280
- // Todo: Support i18nized or canonical aliases (but don't
3281
- // over-trim in such cases)
3282
-
3283
- const rawSearch = (iilRaw('startEnd', browseFieldSetStartEndIdx + 1) || '').trim();
3284
- const [startFull, endFull] = rawSearch.split(rangeSep);
3285
-
3286
- if (endFull !== undefined) {
3287
- const startPartVals = startFull.split(partSep);
3288
- const endPartVals = endFull.split(partSep);
3289
- const startEndDiff = startPartVals.length - endPartVals.length;
3290
-
3291
- if (startEndDiff > 0) {
3292
- // e.g., 5:42:7 - 8 only gets verses 7-8
3293
- endPartVals.unshift(...startPartVals.slice(0, startEndDiff));
3294
- } else if (startEndDiff < 0) {
3295
- // e.g., 5 - 6:2:1 gets all of book 5 to 6:2:1
3296
- // Todo: We should fill with '0' but since that often
3297
- // doesn't find anything, we default for now to '1'.
3298
- startPartVals.push(...Array.from({
3299
- length: -startEndDiff
3300
- }).fill('1'));
3301
- }
3302
-
3303
- console.log('startPartVals', startPartVals);
3304
- console.log('endPartVals', endPartVals);
3305
- startPartVals.forEach((startPartVal, i) => {
3306
- const endPartVal = endPartVals[i];
3307
- $p.set(`${$p.get('work')}-start${browseFieldSetStartEndIdx + 1}-${i + 1}`, startPartVal, true);
3308
- $p.set(`${$p.get('work')}-end${browseFieldSetStartEndIdx + 1}-${i + 1}`, endPartVal, true);
3309
- });
3310
- }
3311
- }
3312
-
3313
- const browseFieldSetIdx = browseFieldSets.findIndex((item, i) => iilRaw('start', i + 1 + '-1'));
3314
- const applicableBrowseFieldSet = browseFieldSets[browseFieldSetIdx];
3315
- const applicableBrowseFieldSetName = setNames[browseFieldSetIdx];
3316
- const applicableBrowseFieldNames = applicableBrowseFieldSet.map(abfs => abfs.fieldName);
3317
- const canonicalBrowseFieldSet = browseFieldSets[0];
3318
- const canonicalBrowseFieldSetName = setNames[0];
3319
- const canonicalBrowseFieldNames = canonicalBrowseFieldSet.map(abfs => abfs.fieldName);
3320
- const fieldSchemaTypes = applicableBrowseFieldSet.map(abfs => abfs.fieldSchema.type);
3321
-
3322
- const buildRangePoint = startOrEnd => applicableBrowseFieldNames.map((bfn, j) => $p.get($p.get('work') + '-' + startOrEnd + (browseFieldSetIdx + 1) + '-' + (j + 1), true));
3323
-
3324
- const starts = buildRangePoint('start');
3325
- const ends = buildRangePoint('end');
3326
- const [hasCaption, caption] = getCaption({
3327
- starts,
3328
- ends,
3329
- applicableBrowseFieldNames,
3330
- heading
3331
- });
3332
- const showInterlinTitles = $pRaw('interlintitle') === '1';
3333
- console.log('rand', ilRaw('rand') === 'yes');
3334
-
3335
- const stripToRawFieldValue = (v, i) => {
3336
- let val;
3337
-
3338
- if (/^\d+$/.test(v) || fieldValueAliasRegex.test(v)) {
3339
- val = getRawFieldValue(v);
3340
- } else {
3341
- const {
3342
- rawFieldValueAliasMap
3343
- } = applicableBrowseFieldSet[i];
3344
- let dealiased;
3345
-
3346
- if (rawFieldValueAliasMap) {
3347
- // Look to dealias
3348
- const fvEntries = Object.entries(rawFieldValueAliasMap);
3349
-
3350
- if (Array.isArray(fvEntries[0][1])) {
3351
- fvEntries.some(([key, arr]) => {
3352
- if (arr.includes(v)) {
3353
- dealiased = key;
3354
- return true;
3355
- }
3356
-
3357
- return false;
3358
- });
3359
- } else {
3360
- fvEntries.some(([key, obj]) => {
3361
- const arr = Object.values(obj);
3362
-
3363
- if (arr.includes(v)) {
3364
- dealiased = key;
3365
- return true;
3366
- }
3367
-
3368
- return false;
3369
- });
3370
- }
3371
- }
3372
-
3373
- val = dealiased === undefined ? v : dealiased;
3374
- }
3375
-
3376
- return fieldSchemaTypes[i] === 'integer' ? Number.parseInt(val) : val;
3377
- };
3378
-
3379
- const unlocalizedWorkName = fileData.name;
3380
- const startsRaw = starts.map(stripToRawFieldValue);
3381
- const endsRaw = ends.map(stripToRawFieldValue);
3382
- let tableData,
3383
- usingServerData = false; // Site owner may have configured to skip (e.g., testing)
3384
-
3385
- if (!skipIndexedDB && // User may have refused, not yet agreed, or are visiting the
3386
- // results page directly where we don't ask for the permissions
3387
- // needed for persistent IndexedDB currently so that people can
3388
- // be brought to a results page without needing to agree to persist
3389
- // through notifications (or however)
3390
- !noIndexedDB) {
3391
- tableData = await new Promise((resolve, reject) => {
3392
- // Todo: Fetch the work in code based on the non-localized `datafileName`
3393
- const dbName = this.namespace + '-textbrowser-cache-data';
3394
- const req = indexedDB.open(dbName);
3395
-
3396
- req.onsuccess = ({
3397
- target: {
3398
- result: db
3399
- }
3400
- }) => {
3401
- const storeName = 'files-to-cache-' + unlocalizedWorkName;
3402
- const trans = db.transaction(storeName);
3403
- const store = trans.objectStore(storeName); // Get among browse field sets by index number within URL params
3404
-
3405
- const index = store.index('browseFields-' + applicableBrowseFieldSetName); // console.log('dbName', dbName);
3406
- // console.log('storeName', storeName);
3407
- // console.log('applicableBrowseFieldSetName', 'browseFields-' + applicableBrowseFieldSetName);
3408
-
3409
- const r = index.getAll(IDBKeyRange.bound(startsRaw, endsRaw));
3410
-
3411
- r.onsuccess = ({
3412
- target: {
3413
- result
3414
- }
3415
- }) => {
3416
- const converted = result.map(r => r.value);
3417
- resolve(converted);
3418
- };
3419
- };
3420
- });
3421
- } else {
3422
- // No need for presorting in indexedDB, given indexes
3423
- const presort = presorts[browseFieldSetIdx]; // Given that we are not currently wishing to add complexity to
3424
- // our PHP code (though it is not a problem with Node.js),
3425
- // we retrieve the whole file and then sort where presorting is
3426
- // needed
3427
- // if (presort || this.noDynamic) {
3428
-
3429
- if (this.noDynamic) {
3430
- ({
3431
- resolved: {
3432
- data: tableData
3433
- }
3434
- } = await JsonRefs.resolveRefs(fileData.file));
3435
- runPresort({
3436
- presort,
3437
- tableData,
3438
- applicableBrowseFieldNames,
3439
- localizedFieldNames
3440
- });
3441
- } else {
3442
- /*
3443
- const jsonURL = Object.entries({
3444
- prefI18n, unlocalizedWorkName, startsRaw, endsRaw
3445
- }).reduce((url, [arg, argVal]) => {
3446
- return url + '&' + arg + '=' + encodeURIComponent((argVal));
3447
- }, `${dynamicBasePath}textbrowser?`);
3448
- */
3449
- const jsonURL = `${dynamicBasePath}textbrowser?${$p.toString()}`;
3450
- tableData = await (await fetch(jsonURL)).json();
3451
- usingServerData = true;
3452
- }
3453
- }
3454
-
3455
- if (!usingServerData && pluginsForWork) {
3456
- fieldInfo.forEach(({
3457
- plugin,
3458
- placement
3459
- }, j) => {
3460
- if (!plugin) {
3461
- return;
3462
- }
3463
-
3464
- tableData.forEach((tr, i) => {
3465
- // Todo: We should pass on other arguments (like `meta` but on `applicableFields`)
3466
- tr.splice(placement, 0, null // `${i}-${j}`);
3467
- );
3468
- });
3469
- });
3470
- fieldInfo.forEach(({
3471
- plugin,
3472
- applicableField,
3473
- fieldLang,
3474
- meta,
3475
- metaApplicableField
3476
- }, j) => {
3477
- if (!plugin) {
3478
- return;
3479
- }
3480
-
3481
- const applicableFieldIdx = fieldInfo.findIndex(({
3482
- field
3483
- }) => {
3484
- return field === applicableField;
3485
- }); // Now safe to pass (and set) `j` value as tr array expanded
3486
-
3487
- tableData.forEach((tr, i) => {
3488
- const applicableFieldText = tr[applicableFieldIdx];
3489
- tr[j] = plugin.getCellData && plugin.getCellData({
3490
- tr,
3491
- tableData,
3492
- i,
3493
- j,
3494
- applicableField,
3495
- fieldInfo,
3496
- applicableFieldIdx,
3497
- applicableFieldText,
3498
- fieldLang,
3499
- getLangDir: rtlDetect.getLangDir,
3500
- meta,
3501
- metaApplicableField,
3502
- $p,
3503
- thisObj: this
3504
- }) || applicableFieldText;
3505
- });
3506
- console.log('applicableFieldIdx', applicableFieldIdx);
3507
- });
3508
- }
3509
-
3510
- const localeDir = rtlDetect.getLangDir(preferredLocale);
3511
- const fieldDirs = fieldLangs.map(langCode => {
3512
- if (!langCode) {
3513
- return null;
3514
- }
3515
-
3516
- const langDir = rtlDetect.getLangDir(langCode);
3517
- return langDir !== localeDir ? langDir : null;
3518
- });
3519
- const templateArgs = {
3520
- tableData,
3521
- $p,
3522
- $pRaw,
3523
- $pRawEsc,
3524
- $pEscArbitrary,
3525
- escapeQuotedCSS,
3526
- escapeCSS,
3527
- escapeHTML,
3528
- l,
3529
- localizedFieldNames,
3530
- fieldLangs,
3531
- fieldDirs,
3532
- caption,
3533
- hasCaption,
3534
- showInterlinTitles,
3535
- determineEnd: determineEnd({
3536
- applicableBrowseFieldNames,
3537
- fieldValueAliasMap,
3538
- fieldValueAliasMapPreferred,
3539
- localizedFieldNames,
3540
- startsRaw,
3541
- endsRaw
3542
- }),
3543
- canonicalBrowseFieldSetName,
3544
- getCanonicalID: getCanonicalID({
3545
- canonicalBrowseFieldNames,
3546
- fieldValueAliasMap,
3547
- fieldValueAliasMapPreferred,
3548
- localizedFieldNames
3549
- }),
3550
- getCellValue: getCellValue({
3551
- fieldValueAliasMapPreferred,
3552
- escapeColumnIndexes,
3553
- escapeHTML
3554
- }),
3555
- checkedAndInterlinearFieldInfo: getCheckedAndInterlinearFieldInfo({
3556
- localizedFieldNames
3557
- }),
3558
- interlinearSeparator: this.interlinearSeparator
3559
- };
3560
- return {
3561
- fieldInfo,
3562
- $p,
3563
- applicableBrowseFieldSet,
3564
- fieldValueAliasMapPreferred,
3565
- lf,
3566
- iil,
3567
- ilRaw,
3568
- browseFieldSets,
3569
- lang,
3570
- metadataObj,
3571
- fileData,
3572
- templateArgs
3573
- };
3574
- };
3575
-
3576
- function getIMFFallbackResults({
3577
- $p,
3578
- lang,
3579
- langs,
3580
- langData,
3581
- fallbackLanguages,
3582
- resultsDisplay,
3583
- basePath = '',
3584
- localeCallback = false
3585
- }) {
3586
- return new Promise((resolve, reject) => {
3587
- const resultsCallback = (...args) => {
3588
- const [l10n] = args;
3589
-
3590
- if (!$p.l10n) {
3591
- $p.l10n = l10n;
3592
- }
3593
-
3594
- return resultsDisplay({
3595
- l: l10n,
3596
- lang,
3597
- fallbackLanguages,
3598
- imfLocales: imf$1.locales,
3599
- $p,
3600
- basePath
3601
- }, ...args);
3602
- };
3603
-
3604
- const imf$1 = imf.IMF({
3605
- languages: lang,
3606
- fallbackLanguages,
3607
-
3608
- localeFileResolver(code) {
3609
- // Todo: For editing of locales, we might instead resolve all
3610
- // `$ref` (as with <https://github.com/whitlockjc/json-refs>) and
3611
- // replace IMF() loadLocales behavior with our own now resolved
3612
- // locales; see https://github.com/jdorn/json-editor/issues/132
3613
- return basePath + langData.localeFileBasePath + langs.find(l => l.code === code).locale.$ref;
3614
- },
3615
-
3616
- async callback(...args) {
3617
- if (localeCallback && localeCallback(...args)) {
3618
- resolve();
3619
- return;
3620
- }
3621
-
3622
- await resultsCallback(...args);
3623
- resolve();
3624
- }
3625
-
3626
- });
3627
- });
3628
- }
3629
-
3630
- /* globals console, location, URL */
3631
- const setServiceWorkerDefaults = (target, source) => {
3632
- target.userJSON = source.userJSON || 'resources/user.json';
3633
- target.languages = source.languages || new URL('../appdata/languages.json', // Todo: Substitute with `import.meta.url`
3634
- new URL('node_modules/textbrowser/resources/index.js', location)).href;
3635
- target.serviceWorkerPath = source.serviceWorkerPath || `sw.js?pathToUserJSON=${encodeURIComponent(target.userJSON)}&stylesheets=${encodeURIComponent(JSON.stringify(target.stylesheets || []))}`;
3636
- target.files = source.files || 'files.json';
3637
- target.namespace = source.namespace || 'textbrowser';
3638
- return target;
3639
- }; // (Unless skipped in code, will wait between install
3640
-
3641
- /* eslint-env worker */
3642
- const {
3643
- ceil
3644
- } = Math;
3645
-
3646
- const arrayChunk = (arr, size) => {
3647
- return [...Array.from({
3648
- length: ceil(arr.length / size)
3649
- })].map((_, i) => {
3650
- const offset = i * size;
3651
- return arr.slice(offset, offset + size);
3652
- });
3653
- }; // Todo: If fetching fails here (or in install), e.g., because activate event
3654
- // never completed
3655
- // to cache, de-register and re-register (?); how to detect if all
3656
- // files in cache?
3657
- // Todo: Check `oldVersion` and run this first if still too old
3658
-
3659
- /**
3660
- * @callback Logger
3661
- * @param {...any} args
3662
- * @returns {void}
3663
- */
3664
-
3665
- /**
3666
- * @param {PlainObject} cfg
3667
- * @param {string} cfg.namespace
3668
- * @param {string[]} cfg.files
3669
- * @param {Logger} cfg.log
3670
- * @param {string} [cfg.basePath=""]
3671
- * @returns {Promise<void>}
3672
- */
3673
-
3674
-
3675
- async function activateCallback({
3676
- namespace,
3677
- files,
3678
- log,
3679
- basePath = ''
3680
- }) {
3681
- // Now we know we have the files cached, we can postpone
3682
- // the `indexedDB` processing (which will work offline
3683
- // anyways); also important to avoid conflicts with
3684
- // already-running versions upon future sw updates
3685
- log('Activate: Callback called');
3686
- const r = await fetch(files);
3687
- const {
3688
- groups
3689
- } = await r.json();
3690
-
3691
- const addJSONFetch = (arr, path) => {
3692
- arr.push((async () => (await fetch(basePath + path)).json())());
3693
- };
3694
-
3695
- const dataFileNames = [];
3696
- const dataFiles = [];
3697
- const schemaFiles = [];
3698
- const metadataFiles = [];
3699
- groups.forEach(({
3700
- files: fileObjs,
3701
- metadataBaseDirectory,
3702
- schemaBaseDirectory
3703
- }) => {
3704
- fileObjs.forEach(({
3705
- file: {
3706
- $ref: filePath
3707
- },
3708
- metadataFile,
3709
- schemaFile,
3710
- name
3711
- }) => {
3712
- // We don't i18nize the name here
3713
- dataFileNames.push(name);
3714
- addJSONFetch(dataFiles, filePath);
3715
- addJSONFetch(metadataFiles, metadataBaseDirectory + '/' + metadataFile);
3716
- addJSONFetch(schemaFiles, schemaBaseDirectory + '/' + schemaFile);
3717
- });
3718
- });
3719
- const promises = await Promise.all([...dataFiles, ...schemaFiles, ...metadataFiles]);
3720
- const chunked = arrayChunk(promises, dataFiles.length);
3721
- const [dataFileResponses, schemaFileResponses, metadataFileResponses] = chunked;
3722
- log('Activate: Files fetched');
3723
- const dbName = namespace + '-textbrowser-cache-data';
3724
- indexedDB.deleteDatabase(dbName);
3725
- return new Promise((resolve, reject) => {
3726
- const req = indexedDB.open(dbName);
3727
- req.addEventListener('upgradeneeded', ({
3728
- target: {
3729
- result: db
3730
- }
3731
- }) => {
3732
- db.onversionchange = () => {
3733
- db.close();
3734
- const err = new Error('versionchange');
3735
- err.type = 'versionchange';
3736
- reject(err);
3737
- };
3738
-
3739
- dataFileResponses.forEach(({
3740
- data: tableRows
3741
- }, i) => {
3742
- const dataFileName = dataFileNames[i];
3743
- const store = db.createObjectStore('files-to-cache-' + dataFileName);
3744
- const schemaFileResponse = schemaFileResponses[i];
3745
- const metadataFileResponse = metadataFileResponses[i];
3746
- const fieldItems = schemaFileResponse.items.items;
3747
- let browseFields = metadataFileResponse.table.browse_fields;
3748
- browseFields = Array.isArray(browseFields) ? browseFields : [browseFields];
3749
- const columnIndexes = [];
3750
- browseFields.forEach(browseFieldSetObj => {
3751
- if (typeof browseFieldSetObj === 'string') {
3752
- browseFieldSetObj = {
3753
- set: [browseFieldSetObj]
3754
- };
3755
- }
3756
-
3757
- if (!browseFieldSetObj.name) {
3758
- browseFieldSetObj.name = browseFieldSetObj.set.join(',');
3759
- }
3760
-
3761
- const browseFieldSetName = browseFieldSetObj.name;
3762
- const browseFieldSetIndexes = browseFieldSetObj.set.map(browseField => {
3763
- // Need to convert to columns for numbers
3764
- // to become valid key paths
3765
- return 'c' + fieldItems.findIndex(item => item.title === browseField);
3766
- });
3767
- columnIndexes.push(...browseFieldSetIndexes);
3768
- log('Activate: Creating index:', dataFileName, 'browseFields-' + browseFieldSetName, browseFieldSetIndexes); // No need for using `presort` as our index will sort anyways
3769
-
3770
- store.createIndex('browseFields-' + browseFieldSetName, browseFieldSetIndexes);
3771
- });
3772
- const uniqueColumnIndexes = [...new Set(columnIndexes)];
3773
- tableRows.forEach((tableRow, j) => {
3774
- // Todo: Optionally send notice when complete
3775
- // To take advantage of indexes on our arrays, we
3776
- // need to transform them to objects! See https://github.com/w3c/IndexedDB/issues/209
3777
- const objRow = {
3778
- value: tableRow
3779
- };
3780
- uniqueColumnIndexes.forEach(colIdx => {
3781
- objRow[colIdx] = tableRow[colIdx.slice(1)];
3782
- }); // log('objRow', objRow);
3783
-
3784
- store.put(objRow, j);
3785
- });
3786
- });
3787
- });
3788
- req.addEventListener('success', ({
3789
- target: {
3790
- result: db
3791
- }
3792
- }) => {
3793
- log('Activate: Database set-up complete', db); // Todo: Replace this with `ready()` check
3794
- // in calling code?
3795
-
3796
- resolve();
3797
- });
3798
-
3799
- const onerr = ({
3800
- error = new Error('dbError')
3801
- }) => {
3802
- error.type = 'dbError';
3803
- reject(error);
3804
- };
3805
-
3806
- req.addEventListener('blocked', onerr);
3807
- req.addEventListener('error', onerr);
3808
- });
3809
- }
3810
-
3811
- /* eslint-disable import/no-commonjs */
3812
-
3813
- const http = require('http');
3814
-
3815
- const fetch$1 = require('node-fetch'); // Problems as `import` since 2.1.2
3816
-
3817
-
3818
- const setGlobalVars = require('indexeddbshim/dist/indexeddbshim-UnicodeIdentifiers-node.js'); // Todo (low): See
3819
- // https://gist.github.com/brettz9/0993fbde6f7352b2bb05f38078cefb29
3820
- // on idea for adapting node-serviceworker to consume entire service-worker
3821
- // server-side and apply fetch listener as Node middleware to retrieve a
3822
- // locally-saved copy of pre-fetched (cached) files during install event
3823
-
3824
-
3825
- const optionDefinitions = [// Node-server-specific
3826
- {
3827
- name: 'nodeActivate',
3828
- type: Boolean
3829
- }, {
3830
- name: 'port',
3831
- type: Number
3832
- }, {
3833
- name: 'domain',
3834
- type: String
3835
- }, {
3836
- name: 'basePath',
3837
- type: String
3838
- }, // Results display (main)
3839
- // `namespace`: (but set below)
3840
- // `skipIndexedDB`: set to `false` below (and the default anyways)
3841
- // `noDynamic`: not used
3842
- // `prefI18n`: will instead be set dynamically per user query
3843
- // `files`: Already set below for service worker
3844
- {
3845
- name: 'interlinearSeparator',
3846
- type: String
3847
- }, {
3848
- name: 'localizeParamNames',
3849
- type: Boolean
3850
- }, {
3851
- name: 'trustFormatHTML',
3852
- type: Boolean
3853
- }, {
3854
- name: 'allowPlugins',
3855
- type: Boolean
3856
- }, // Results display (template)
3857
- {
3858
- name: 'showEmptyInterlinear',
3859
- type: Boolean
3860
- }, {
3861
- name: 'showTitleOnSingleInterlinear',
3862
- type: Boolean
3863
- }, // Service worker
3864
- {
3865
- name: 'serviceWorkerPath',
3866
- type: String,
3867
- defaultOption: true
3868
- }, {
3869
- name: 'userJSON',
3870
- type: String
3871
- }, {
3872
- name: 'languages',
3873
- type: String
3874
- }, {
3875
- name: 'files',
3876
- type: String
3877
- }, {
3878
- name: 'namespace',
3879
- type: String
3880
- }, {
3881
- name: 'expressServer',
3882
- type: String
3883
- }];
3884
-
3885
- const userParams = require('command-line-args')(optionDefinitions);
3886
-
3887
- const port = 'port' in userParams ? userParams.port : 8000;
3888
- const domain = userParams.domain || `localhost`;
3889
- const basePath = userParams.basePath || `http://${domain}${port ? ':' + port : ''}/`;
3890
-
3891
- const userParamsWithDefaults = _objectSpread2(_objectSpread2(_objectSpread2({}, setServiceWorkerDefaults({}, {
3892
- namespace: 'textbrowser',
3893
- files: `${basePath}files.json`,
3894
- // `files` must be absolute path for node-fetch
3895
- languages: `${basePath}node_modules/textbrowser/appdata/languages.json`,
3896
- serviceWorkerPath: `${basePath}sw.js?pathToUserJSON=${encodeURIComponent(userParams.userJSON || '')}`
3897
- })), userParams), {}, {
3898
- log(...args) {
3899
- console.log(...args);
3900
- },
3901
-
3902
- nodeActivate: undefined,
3903
- port: undefined,
3904
- skipIndexedDB: false,
3905
- // Not relevant here
3906
- noDynamic: false // Not relevant here
3907
-
3908
- /*
3909
- Not in use:
3910
- logger: {
3911
- addLogEntry ({text}) {
3912
- console.log(`Log: ${text}`);
3913
- },
3914
- dbError ({
3915
- type,
3916
- escapedErrorMessage
3917
- }) {
3918
- throw new Error(`Worker aborted: error type ${type}; ${escapedErrorMessage}`);
3919
- }
3920
- } */
3921
-
3922
- });
3923
-
3924
- console.log('userParamsWithDefaults', userParamsWithDefaults);
3925
- setGlobalVars(null, {
3926
- checkOrigin: false
3927
- }); // Adds `indexedDB` and `IDBKeyRange` to global in Node
3928
-
3929
- if (userParams.nodeActivate) {
3930
- global.fetch = fetch$1;
3931
-
3932
- (async () => {
3933
- await activateCallback(_objectSpread2(_objectSpread2({}, userParamsWithDefaults), {}, {
3934
- basePath
3935
- }));
3936
- console.log('Activated');
3937
- })();
3938
- }
3939
-
3940
- console.log('past activate check');
3941
- global.DOMParser = require('dom-parser'); // potentially used within resultsDisplay.js
3942
-
3943
- const statik = require('@brettz9/node-static');
3944
- /* eslint-enable import/no-commonjs */
3945
-
3946
-
3947
- const fileServer = new statik.Server(); // Pass path; otherwise uses current directory
3948
-
3949
- let langData, languagesInstance;
3950
- const srv = http.createServer(async (req, res) => {
3951
- var _req$headers$acceptL, _req$headers$acceptL2;
3952
-
3953
- // console.log('URL::', new URL(req.url));
3954
- const {
3955
- pathname,
3956
- search
3957
- } = new URL(req.url, basePath);
3958
-
3959
- if (pathname !== '/textbrowser' || !search) {
3960
- const staticServer = () => {
3961
- if (pathname.includes('.git')) {
3962
- req.url = '/index.html';
3963
- }
3964
-
3965
- fileServer.serve(req, res);
3966
- };
3967
-
3968
- if (userParams.expressServer) {
3969
- // eslint-disable-next-line node/global-require, import/no-dynamic-require
3970
- const server = require(userParams.expressServer)();
3971
-
3972
- server.get('*', staticServer);
3973
- server(req, res);
3974
- return;
3975
- }
3976
-
3977
- req.addListener('end', staticServer).resume();
3978
- /*
3979
- res.writeHead(404, {'Content-Type': 'text/html'});
3980
- res.end('<h1>File not found</h1>');
3981
- */
3982
-
3983
- return;
3984
- }
3985
-
3986
- const languages = ((_req$headers$acceptL = (_req$headers$acceptL2 = req.headers['accept-language']) === null || _req$headers$acceptL2 === void 0 ? void 0 : _req$headers$acceptL2.replace(/;q=.*?$/, '')) !== null && _req$headers$acceptL !== void 0 ? _req$headers$acceptL : 'en-US').split(',');
3987
- global.navigator = {
3988
- language: languages[0],
3989
- languages
3990
- };
3991
- const $p = new IntlURLSearchParams({
3992
- params: search
3993
- });
3994
-
3995
- if (!langData || !languagesInstance) {
3996
- langData = await simpleGetJson.getJSON(userParamsWithDefaults.languages);
3997
- languagesInstance = new Languages({
3998
- langData
3999
- });
4000
- }
4001
-
4002
- const {
4003
- lang,
4004
- langs,
4005
- fallbackLanguages
4006
- } = languagesInstance.getLanguageInfo({
4007
- $p
4008
- });
4009
- getIMFFallbackResults({
4010
- $p,
4011
- basePath,
4012
- lang,
4013
- langs,
4014
- fallbackLanguages,
4015
- langData,
4016
-
4017
- async resultsDisplay(resultsArgs, ...args) {
4018
- const serverOutput = $p.get('serverOutput', true);
4019
- let allowPlugins = $p.get('allowPlugins', true);
4020
-
4021
- if (allowPlugins === '0') {
4022
- allowPlugins = false;
4023
- }
4024
-
4025
- const isHTML = serverOutput === 'html';
4026
- res.writeHead(200, {
4027
- 'Content-Type': isHTML ? 'text/html;charset=utf8' : 'application/json;charset=utf8'
4028
- });
4029
- resultsArgs = _objectSpread2(_objectSpread2({}, resultsArgs), {}, {
4030
- skipIndexedDB: false,
4031
- allowPlugins,
4032
- serverOutput,
4033
- langData,
4034
- prefI18n: $p.get('prefI18n', true)
4035
- }); // Todo: Move sw-sample.js to bahaiwritings and test
4036
-
4037
- const result = await resultsDisplayServer.call(_objectSpread2(_objectSpread2({}, userParamsWithDefaults), {}, {
4038
- lang,
4039
- langs,
4040
- fallbackLanguages
4041
- }), resultsArgs, ...args);
4042
- res.end(isHTML ? result : JSON.stringify(result));
4043
- }
4044
-
4045
- });
4046
- });
4047
-
4048
- if (!userParams.domain) {
4049
- srv.listen(port);
4050
- } else {
4051
- srv.listen(port, userParams.domain);
4052
- }