textbrowser 0.47.0 → 0.48.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintignore +2 -1
- package/.eslintrc.cjs +3 -1
- package/CHANGES.md +11 -0
- package/README.md +2 -6
- package/dist/WorkInfo-es.js +978 -345
- package/dist/activateCallback-es.js +6 -2
- package/dist/assets/index-_11XnUty.css +9 -0
- package/dist/assets/languages-1sAACTKG.json +67 -0
- package/dist/index-es.js +2738 -1134
- package/dist/index-es.min.js +4 -4
- package/dist/sw-helper.js +314 -0
- package/index.html +2 -0
- package/package.json +46 -45
- package/resources/activateCallback.js +6 -2
- package/resources/index.js +6 -14
- package/resources/resultsDisplay.js +49 -40
- package/resources/templates/languageSelect.js +3 -3
- package/resources/templates/resultsDisplayServerOrClient.js +76 -65
- package/resources/templates/workDisplay.js +123 -103
- package/resources/templates/workSelect.js +4 -4
- package/resources/utils/IntlURLSearchParams.js +4 -1
- package/resources/utils/Languages.js +6 -1
- package/resources/utils/Metadata.js +22 -21
- package/resources/utils/Plugin.js +4 -4
- package/resources/utils/ServiceWorker.js +21 -14
- package/resources/utils/WorkInfo.js +10 -5
- package/resources/utils/dialogs.js +2 -1
- package/resources/utils/getLocaleFallbackResults.js +6 -3
- package/resources/utils/sanitize.js +3 -3
- package/resources/workDisplay.js +13 -14
- package/resources/workSelect.js +3 -2
- package/server/main.js +6 -5
- package/sw-sample.js +5 -307
- package/dist/index-umd.js +0 -18864
- package/dist/index-umd.min.js +0 -19
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/* eslint-env browser */
|
|
2
|
-
import
|
|
2
|
+
import Locale from 'intl-locale-textinfo-polyfill';
|
|
3
3
|
import JsonRefs from 'json-refs';
|
|
4
4
|
import {jml} from 'jamilih';
|
|
5
5
|
|
|
@@ -10,9 +10,12 @@ import {
|
|
|
10
10
|
} from './utils/Metadata.js';
|
|
11
11
|
import {Languages} from './utils/Languages.js';
|
|
12
12
|
import {getWorkData} from './utils/WorkInfo.js';
|
|
13
|
-
// Keep this as the last import for Rollup
|
|
14
13
|
|
|
15
|
-
const
|
|
14
|
+
const getLangDir = (locale) => {
|
|
15
|
+
const {direction} = new Locale(locale).textInfo;
|
|
16
|
+
return direction;
|
|
17
|
+
};
|
|
18
|
+
|
|
16
19
|
const fieldValueAliasRegex = /^.* \((.*?)\)$/;
|
|
17
20
|
|
|
18
21
|
const getRawFieldValue = (v) => {
|
|
@@ -62,7 +65,7 @@ const setAnchor = ({
|
|
|
62
65
|
}
|
|
63
66
|
}
|
|
64
67
|
if (anchors.length) {
|
|
65
|
-
const escapeSelectorAttValue = (str) => (str || '').
|
|
68
|
+
const escapeSelectorAttValue = (str) => (str || '').replaceAll(/["\\]/g, '\\$&');
|
|
66
69
|
const escapedRow = escapeSelectorAttValue(anchors.join('-'));
|
|
67
70
|
const escapedCol = anchorField
|
|
68
71
|
? escapeSelectorAttValue(anchorField)
|
|
@@ -117,6 +120,11 @@ export const resultsDisplayClient = async function resultsDisplayClient (args) {
|
|
|
117
120
|
});
|
|
118
121
|
};
|
|
119
122
|
|
|
123
|
+
/**
|
|
124
|
+
* This is server-only code, but kept here as the function is similar.
|
|
125
|
+
* @param {{serverOutput: "jamilih"|"html"|"json"}} args
|
|
126
|
+
* @returns {Promise<Array|string>}
|
|
127
|
+
*/
|
|
120
128
|
export const resultsDisplayServer = async function resultsDisplayServer (args) {
|
|
121
129
|
const {
|
|
122
130
|
templateArgs
|
|
@@ -168,7 +176,7 @@ export const resultsDisplayServerOrClient = async function resultsDisplayServerO
|
|
|
168
176
|
fieldValueAliasMap, fieldValueAliasMapPreferred, localizedFieldNames,
|
|
169
177
|
canonicalBrowseFieldNames
|
|
170
178
|
}) => ({
|
|
171
|
-
tr, foundState
|
|
179
|
+
tr // , foundState
|
|
172
180
|
}) => {
|
|
173
181
|
return canonicalBrowseFieldNames.map((fieldName) => {
|
|
174
182
|
const idx = localizedFieldNames.indexOf(fieldName);
|
|
@@ -259,13 +267,13 @@ export const resultsDisplayServerOrClient = async function resultsDisplayServerO
|
|
|
259
267
|
} while (field);
|
|
260
268
|
checkedFields = checkedFields.filter((cf) => localizedFieldNames.includes(cf));
|
|
261
269
|
const checkedFieldIndexes = checkedFields.map((cf) => localizedFieldNames.indexOf(cf));
|
|
262
|
-
const allInterlinearColIndexes = checkedFieldIndexes.map((cfi
|
|
270
|
+
const allInterlinearColIndexes = checkedFieldIndexes.map((cfi) => {
|
|
263
271
|
const interlin = $p.get('interlin' + (cfi + 1), true);
|
|
264
|
-
return interlin && interlin.split(/\s*,\s*/).map((col) =>
|
|
272
|
+
return interlin && interlin.split(/\s*,\s*/).map((col) => {
|
|
265
273
|
// Todo: Avoid this when known to be integer or if string, though allow
|
|
266
274
|
// string to be treated as number if config is set.
|
|
267
|
-
Number.parseInt(col) - 1
|
|
268
|
-
).filter((n) => !Number.isNaN(n));
|
|
275
|
+
return Number.parseInt(col) - 1;
|
|
276
|
+
}).filter((n) => !Number.isNaN(n));
|
|
269
277
|
});
|
|
270
278
|
return [checkedFields, checkedFieldIndexes, allInterlinearColIndexes];
|
|
271
279
|
};
|
|
@@ -294,8 +302,8 @@ export const resultsDisplayServerOrClient = async function resultsDisplayServerO
|
|
|
294
302
|
);
|
|
295
303
|
*/
|
|
296
304
|
const startSep = Templates.resultsDisplayServerOrClient.startSeparator({l});
|
|
297
|
-
const innerBrowseFieldSeparator = Templates.resultsDisplayServerOrClient
|
|
298
|
-
|
|
305
|
+
const innerBrowseFieldSeparator = Templates.resultsDisplayServerOrClient.
|
|
306
|
+
innerBrowseFieldSeparator({l});
|
|
299
307
|
|
|
300
308
|
const buildRanges = () => {
|
|
301
309
|
const endVals = [];
|
|
@@ -354,9 +362,9 @@ export const resultsDisplayServerOrClient = async function resultsDisplayServerO
|
|
|
354
362
|
if (fieldValueAliasMap) {
|
|
355
363
|
Object.entries(fieldValueAliasMap).forEach(([key, val]) => {
|
|
356
364
|
if (Array.isArray(val)) {
|
|
357
|
-
fieldValueAliasMap[key] = val.map((value) =>
|
|
358
|
-
Templates.resultsDisplayServerOrClient.fieldValueAlias({key, value})
|
|
359
|
-
);
|
|
365
|
+
fieldValueAliasMap[key] = val.map((value) => {
|
|
366
|
+
return Templates.resultsDisplayServerOrClient.fieldValueAlias({key, value});
|
|
367
|
+
});
|
|
360
368
|
return;
|
|
361
369
|
}
|
|
362
370
|
if (val && typeof val === 'object') {
|
|
@@ -420,7 +428,7 @@ export const resultsDisplayServerOrClient = async function resultsDisplayServerO
|
|
|
420
428
|
const $pEscArbitrary = (param) => escapeHTML($p.get(param, true));
|
|
421
429
|
|
|
422
430
|
// Not currently in use
|
|
423
|
-
const escapeQuotedCSS = (s) => s.
|
|
431
|
+
const escapeQuotedCSS = (s) => s.replaceAll('\\', '\\\\').replaceAll('"', '\\"');
|
|
424
432
|
|
|
425
433
|
const {
|
|
426
434
|
fileData, workI18n, getFieldAliasOrName, schemaObj, metadataObj,
|
|
@@ -464,7 +472,7 @@ export const resultsDisplayServerOrClient = async function resultsDisplayServerO
|
|
|
464
472
|
console.log('pluginsForWork', pluginsForWork);
|
|
465
473
|
const {lang} = this; // array with first item as preferred
|
|
466
474
|
pluginsForWork.iterateMappings(({
|
|
467
|
-
plugin,
|
|
475
|
+
// plugin,
|
|
468
476
|
pluginName, pluginLang,
|
|
469
477
|
onByDefaultDefault,
|
|
470
478
|
placement, applicableFields, meta
|
|
@@ -564,11 +572,11 @@ export const resultsDisplayServerOrClient = async function resultsDisplayServerO
|
|
|
564
572
|
prefI18n !== 'false' && this.localizeParamNames
|
|
565
573
|
);
|
|
566
574
|
const lParam = localizeParamNames
|
|
567
|
-
? key => l(['params', key])
|
|
568
|
-
: key => key;
|
|
575
|
+
? (key) => l(['params', key])
|
|
576
|
+
: (key) => key;
|
|
569
577
|
const lIndexedParam = localizeParamNames
|
|
570
|
-
? key => l(['params', 'indexed', key])
|
|
571
|
-
: key => key;
|
|
578
|
+
? (key) => l(['params', 'indexed', key])
|
|
579
|
+
: (key) => key;
|
|
572
580
|
const lParamRaw = localizeParamNames
|
|
573
581
|
? (key, suffix = '') => $p.get(lParam(key) + suffix, true)
|
|
574
582
|
: (key, suffix = '') => $p.get(key + suffix, true);
|
|
@@ -577,9 +585,9 @@ export const resultsDisplayServerOrClient = async function resultsDisplayServerO
|
|
|
577
585
|
: (key, suffix = '') => $p.get($p.get('work') + '-' + key + suffix, true);
|
|
578
586
|
|
|
579
587
|
// Now that we know `browseFieldSets`, we can parse `startEnd`
|
|
580
|
-
const browseFieldSetStartEndIdx = browseFieldSets.findIndex((item, i) =>
|
|
581
|
-
lIndexedParamRaw('startEnd', (i + 1))
|
|
582
|
-
);
|
|
588
|
+
const browseFieldSetStartEndIdx = browseFieldSets.findIndex((item, i) => {
|
|
589
|
+
return lIndexedParamRaw('startEnd', (i + 1));
|
|
590
|
+
});
|
|
583
591
|
if (browseFieldSetStartEndIdx !== -1) {
|
|
584
592
|
// Todo: i18nize (by work and/or by whole app?)
|
|
585
593
|
const rangeSep = '-';
|
|
@@ -614,29 +622,30 @@ export const resultsDisplayServerOrClient = async function resultsDisplayServerO
|
|
|
614
622
|
}
|
|
615
623
|
}
|
|
616
624
|
|
|
617
|
-
const browseFieldSetIdx = browseFieldSets.findIndex((item, i) =>
|
|
618
|
-
lIndexedParamRaw('start', (i + 1) + '-1')
|
|
619
|
-
);
|
|
625
|
+
const browseFieldSetIdx = browseFieldSets.findIndex((item, i) => {
|
|
626
|
+
return lIndexedParamRaw('start', (i + 1) + '-1');
|
|
627
|
+
});
|
|
620
628
|
const applicableBrowseFieldSet = browseFieldSets[browseFieldSetIdx];
|
|
621
629
|
const applicableBrowseFieldSetName = setNames[browseFieldSetIdx];
|
|
622
|
-
const applicableBrowseFieldNames = applicableBrowseFieldSet.map((abfs) =>
|
|
623
|
-
abfs.fieldName
|
|
624
|
-
);
|
|
630
|
+
const applicableBrowseFieldNames = applicableBrowseFieldSet.map((abfs) => {
|
|
631
|
+
return abfs.fieldName;
|
|
632
|
+
});
|
|
625
633
|
|
|
626
634
|
const canonicalBrowseFieldSet = browseFieldSets[0];
|
|
627
635
|
const canonicalBrowseFieldSetName = setNames[0];
|
|
628
|
-
const canonicalBrowseFieldNames = canonicalBrowseFieldSet.map((abfs) =>
|
|
629
|
-
abfs.fieldName
|
|
630
|
-
);
|
|
636
|
+
const canonicalBrowseFieldNames = canonicalBrowseFieldSet.map((abfs) => {
|
|
637
|
+
return abfs.fieldName;
|
|
638
|
+
});
|
|
631
639
|
|
|
632
640
|
const fieldSchemaTypes = applicableBrowseFieldSet.map((abfs) => abfs.fieldSchema.type);
|
|
633
|
-
const buildRangePoint = (startOrEnd) =>
|
|
634
|
-
applicableBrowseFieldNames.map((bfn, j) =>
|
|
635
|
-
$p.get(
|
|
641
|
+
const buildRangePoint = (startOrEnd) => {
|
|
642
|
+
return applicableBrowseFieldNames.map((bfn, j) => {
|
|
643
|
+
return $p.get(
|
|
636
644
|
$p.get('work') + '-' + startOrEnd + (browseFieldSetIdx + 1) + '-' + (j + 1),
|
|
637
645
|
true
|
|
638
|
-
)
|
|
639
|
-
);
|
|
646
|
+
);
|
|
647
|
+
});
|
|
648
|
+
};
|
|
640
649
|
const starts = buildRangePoint('start');
|
|
641
650
|
const ends = buildRangePoint('end');
|
|
642
651
|
|
|
@@ -696,7 +705,7 @@ export const resultsDisplayServerOrClient = async function resultsDisplayServerO
|
|
|
696
705
|
// through notifications (or however)
|
|
697
706
|
!noIndexedDB
|
|
698
707
|
) {
|
|
699
|
-
tableData = await new Promise((resolve
|
|
708
|
+
tableData = await new Promise((resolve) => {
|
|
700
709
|
// Todo: Fetch the work in code based on the non-localized `datafileName`
|
|
701
710
|
const dbName = this.namespace + '-textbrowser-cache-data';
|
|
702
711
|
const req = indexedDB.open(dbName);
|
|
@@ -747,11 +756,11 @@ export const resultsDisplayServerOrClient = async function resultsDisplayServerO
|
|
|
747
756
|
}
|
|
748
757
|
}
|
|
749
758
|
if (!usingServerData && pluginsForWork) {
|
|
750
|
-
fieldInfo.forEach(({plugin, placement}
|
|
759
|
+
fieldInfo.forEach(({plugin, placement}) => {
|
|
751
760
|
if (!plugin) {
|
|
752
761
|
return;
|
|
753
762
|
}
|
|
754
|
-
tableData.forEach((tr
|
|
763
|
+
tableData.forEach((tr) => {
|
|
755
764
|
// Todo: We should pass on other arguments (like `meta` but on `applicableFields`)
|
|
756
765
|
tr.splice(
|
|
757
766
|
placement,
|
|
@@ -19,9 +19,9 @@ export default {
|
|
|
19
19
|
});
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
|
-
}, langs.map(({code}) =>
|
|
23
|
-
['option', {value: code}, [languages.getLanguageFromCode(code)]]
|
|
24
|
-
)]
|
|
22
|
+
}, langs.map(({code}) => {
|
|
23
|
+
return ['option', {value: code}, [languages.getLanguageFromCode(code)]];
|
|
24
|
+
})]
|
|
25
25
|
], $('#main'));
|
|
26
26
|
if (history.state && typeof history.state === 'object') {
|
|
27
27
|
formDeserialize(document.querySelector('#languageSelectionContainer'), history.state);
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/* globals console, DOMParser */
|
|
2
1
|
import Templates from './index.js';
|
|
3
2
|
|
|
4
3
|
export default {
|
|
@@ -34,7 +33,8 @@ export default {
|
|
|
34
33
|
return `<span class="interlintitle">${val}</span>${colonSpace}`;
|
|
35
34
|
},
|
|
36
35
|
styles ({
|
|
37
|
-
$p, $pRaw, $pRawEsc, $pEscArbitrary, escapeQuotedCSS,
|
|
36
|
+
$p, $pRaw, $pRawEsc, $pEscArbitrary, // escapeQuotedCSS,
|
|
37
|
+
escapeCSS,
|
|
38
38
|
tableWithFixedHeaderAndFooter, checkedFieldIndexes, hasCaption
|
|
39
39
|
}) {
|
|
40
40
|
const colorEsc = !$p.has('color', true) || $p.get('color', true) === '#'
|
|
@@ -141,8 +141,8 @@ div.inner-caption {
|
|
|
141
141
|
}
|
|
142
142
|
${escapeCSS($pEscArbitrary('pagecss') || '')}
|
|
143
143
|
` +
|
|
144
|
-
checkedFieldIndexes.map((idx, i) =>
|
|
145
|
-
($pRaw('header') === 'y'
|
|
144
|
+
checkedFieldIndexes.map((idx, i) => {
|
|
145
|
+
return ($pRaw('header') === 'y'
|
|
146
146
|
? (tableWithFixedHeaderAndFooter
|
|
147
147
|
? `.thead .th:nth-child(${i + 1}) div.th-inner, `
|
|
148
148
|
: `.thead .th:nth-child(${i + 1}), `)
|
|
@@ -156,14 +156,17 @@ ${escapeCSS($pEscArbitrary('pagecss') || '')}
|
|
|
156
156
|
`{
|
|
157
157
|
${$pEscArbitrary('css' + (idx + 1))}
|
|
158
158
|
}
|
|
159
|
-
|
|
159
|
+
`;
|
|
160
|
+
}).join('') +
|
|
160
161
|
|
|
161
|
-
($pEscArbitrary('interlintitle_css')
|
|
162
|
-
|
|
163
|
-
.
|
|
164
|
-
|
|
165
|
-
}
|
|
166
|
-
|
|
162
|
+
($pEscArbitrary('interlintitle_css')
|
|
163
|
+
? `
|
|
164
|
+
/* http://salzerdesign.com/test/fixedTable.html */
|
|
165
|
+
.interlintitle {
|
|
166
|
+
${escapeCSS($pEscArbitrary('interlintitle_css'))}
|
|
167
|
+
}
|
|
168
|
+
`
|
|
169
|
+
: '') +
|
|
167
170
|
(bgcolorEsc
|
|
168
171
|
? `
|
|
169
172
|
body {
|
|
@@ -357,16 +360,18 @@ body {
|
|
|
357
360
|
}),
|
|
358
361
|
tableWrap([
|
|
359
362
|
addChildren(tableElem, [
|
|
360
|
-
(caption
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
['div', {class: '
|
|
365
|
-
['
|
|
363
|
+
(caption
|
|
364
|
+
? addChildren(captionElem, [
|
|
365
|
+
caption,
|
|
366
|
+
tableWithFixedHeaderAndFooter
|
|
367
|
+
? ['div', {class: 'zupa1'}, [
|
|
368
|
+
['div', {class: 'inner-caption'}, [
|
|
369
|
+
['span', [caption]]
|
|
370
|
+
]]
|
|
366
371
|
]]
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
372
|
+
: ''
|
|
373
|
+
])
|
|
374
|
+
: ''),
|
|
370
375
|
/*
|
|
371
376
|
// Works but quirky, e.g., `color` doesn't work (as also
|
|
372
377
|
// confirmed per https://quirksmode.org/css/css2/columns.html)
|
|
@@ -376,54 +381,60 @@ body {
|
|
|
376
381
|
)
|
|
377
382
|
),
|
|
378
383
|
*/
|
|
379
|
-
($pRaw('header') !== '0'
|
|
380
|
-
addChildren(
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
cf
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
return addChildren(thElem, [
|
|
390
|
-
cf,
|
|
391
|
-
(tableWithFixedHeaderAndFooter
|
|
392
|
-
? ['div', {class: 'zupa1'}, [
|
|
393
|
-
['div', {class: 'th-inner'}, [
|
|
394
|
-
['span', [cf]]
|
|
395
|
-
]]
|
|
396
|
-
]]
|
|
384
|
+
($pRaw('header') !== '0'
|
|
385
|
+
? addChildren(theadElem, [
|
|
386
|
+
addChildren(
|
|
387
|
+
trElem,
|
|
388
|
+
checkedFields.map((cf, i) => {
|
|
389
|
+
const interlinearColIndexes = allInterlinearColIndexes[i];
|
|
390
|
+
cf = escapeHTML(cf) + (interlinearColIndexes
|
|
391
|
+
? l('comma-space') + interlinearColIndexes.map((idx) => {
|
|
392
|
+
return localizedFieldNames[idx];
|
|
393
|
+
}).join(l('comma-space'))
|
|
397
394
|
: ''
|
|
398
|
-
)
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
const interlinearColIndexes = allInterlinearColIndexes[i];
|
|
407
|
-
cf = escapeHTML(cf) + (interlinearColIndexes
|
|
408
|
-
? l('comma-space') + interlinearColIndexes.map((idx) =>
|
|
409
|
-
localizedFieldNames[idx]
|
|
410
|
-
).join(l('comma-space'))
|
|
411
|
-
: ''
|
|
412
|
-
);
|
|
413
|
-
return addChildren(thElem, [
|
|
414
|
-
cf,
|
|
415
|
-
(tableWithFixedHeaderAndFooter
|
|
416
|
-
? ['div', {class: 'zupa1'}, [
|
|
417
|
-
['div', {class: 'th-inner'}, [
|
|
418
|
-
['span', [cf]]
|
|
395
|
+
);
|
|
396
|
+
return addChildren(thElem, [
|
|
397
|
+
cf,
|
|
398
|
+
(tableWithFixedHeaderAndFooter
|
|
399
|
+
? ['div', {class: 'zupa1'}, [
|
|
400
|
+
['div', {class: 'th-inner'}, [
|
|
401
|
+
['span', [cf]]
|
|
402
|
+
]]
|
|
419
403
|
]]
|
|
420
|
-
|
|
404
|
+
: ''
|
|
405
|
+
)
|
|
406
|
+
]);
|
|
407
|
+
})
|
|
408
|
+
)
|
|
409
|
+
])
|
|
410
|
+
: ''),
|
|
411
|
+
($pRaw('footer') && $pRaw('footer') !== '0'
|
|
412
|
+
? addChildren(tfootElem, [
|
|
413
|
+
addChildren(
|
|
414
|
+
trElem,
|
|
415
|
+
checkedFields.map((cf, i) => {
|
|
416
|
+
const interlinearColIndexes = allInterlinearColIndexes[i];
|
|
417
|
+
cf = escapeHTML(cf) + (interlinearColIndexes
|
|
418
|
+
? l('comma-space') + interlinearColIndexes.map((idx) => {
|
|
419
|
+
return localizedFieldNames[idx];
|
|
420
|
+
}).join(l('comma-space'))
|
|
421
421
|
: ''
|
|
422
|
-
)
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
422
|
+
);
|
|
423
|
+
return addChildren(thElem, [
|
|
424
|
+
cf,
|
|
425
|
+
(tableWithFixedHeaderAndFooter
|
|
426
|
+
? ['div', {class: 'zupa1'}, [
|
|
427
|
+
['div', {class: 'th-inner'}, [
|
|
428
|
+
['span', [cf]]
|
|
429
|
+
]]
|
|
430
|
+
]]
|
|
431
|
+
: ''
|
|
432
|
+
)
|
|
433
|
+
]);
|
|
434
|
+
})
|
|
435
|
+
)
|
|
436
|
+
])
|
|
437
|
+
: ''),
|
|
427
438
|
addChildren(tbodyElem, outArr)
|
|
428
439
|
])
|
|
429
440
|
])
|