ochre-sdk 1.0.76 → 1.0.78
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/dist/constants.d.mts +13 -14
- package/dist/fetchers/gallery.d.mts +2 -3
- package/dist/fetchers/gallery.mjs +2 -1
- package/dist/fetchers/item-children.d.mts +3 -4
- package/dist/fetchers/item-children.mjs +2 -1
- package/dist/fetchers/item-links.d.mts +2 -3
- package/dist/fetchers/item-links.mjs +2 -1
- package/dist/fetchers/item-ocr-data.d.mts +2 -3
- package/dist/fetchers/item.d.mts +7 -8
- package/dist/fetchers/item.mjs +2 -1
- package/dist/fetchers/set/items.d.mts +2 -3
- package/dist/fetchers/set/items.mjs +2 -1
- package/dist/fetchers/set/property-values.d.mts +2 -3
- package/dist/fetchers/set/property-values.mjs +56 -49
- package/dist/fetchers/website-metadata.d.mts +2 -3
- package/dist/fetchers/website.d.mts +2 -3
- package/dist/getters.d.mts +40 -41
- package/dist/getters.mjs +6 -31
- package/dist/helpers.d.mts +3 -4
- package/dist/parsers/helpers.d.mts +16 -17
- package/dist/parsers/index.d.mts +19 -19
- package/dist/parsers/index.mjs +65 -48
- package/dist/parsers/mdx.d.mts +3 -4
- package/dist/parsers/multilingual.d.mts +9 -10
- package/dist/parsers/string.d.mts +5 -6
- package/dist/parsers/string.mjs +124 -146
- package/dist/parsers/website/index.d.mts +4 -5
- package/dist/parsers/website/index.mjs +598 -637
- package/dist/parsers/website/reader.d.mts +3 -4
- package/dist/query.d.mts +3 -4
- package/dist/schemas.d.mts +9 -10
- package/dist/types/index.d.mts +118 -119
- package/dist/types/website.d.mts +36 -38
- package/dist/utilities.d.mts +9 -10
- package/dist/utilities.mjs +28 -25
- package/dist/xml/dates.d.mts +13 -0
- package/dist/xml/dates.mjs +49 -0
- package/dist/xml/metadata.d.mts +3 -4
- package/dist/xml/schemas.d.mts +8 -9
- package/dist/xml/schemas.mjs +20 -27
- package/dist/xml/types.d.mts +129 -131
- package/package.json +13 -12
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { componentSchema } from "../../schemas.mjs";
|
|
2
2
|
import { parseXMLContent } from "../string.mjs";
|
|
3
|
-
import { cleanObject, parseLicense, parseStringContent } from "../helpers.mjs";
|
|
3
|
+
import { cleanObject, parseContentLike, parseLicense, parseStringContent } from "../helpers.mjs";
|
|
4
4
|
import { parseBibliographyList, parseIdentification, parseLinks, parseMetadata, parseMetadataLanguages, parseNotes, parsePersonList, parseSimplifiedProperties, resolveDefaultLanguage, resolveLanguages } from "../index.mjs";
|
|
5
5
|
import { websitePresentationReader } from "./reader.mjs";
|
|
6
6
|
import * as v from "valibot";
|
|
@@ -45,28 +45,34 @@ function prefixSlug(slug, slugPrefix) {
|
|
|
45
45
|
if (slug === "") return slugPrefix;
|
|
46
46
|
return `${slugPrefix}/${slug}`;
|
|
47
47
|
}
|
|
48
|
+
function collectSegmentPageSlugs(trees, options, pageSlugsByUuid, segmentSlugPrefix) {
|
|
49
|
+
for (const tree of trees) {
|
|
50
|
+
const segmentSlug = tree.identification.abbreviation == null ? null : parseStringContent(tree.identification.abbreviation, options);
|
|
51
|
+
if (segmentSlug == null) throw new Error(`Slug not found for segment website (website uuid “${tree.uuid}”)`, { cause: tree });
|
|
52
|
+
collectWebsitePageSlugs(tree.items?.resource, options, prefixSlug(segmentSlug, segmentSlugPrefix), pageSlugsByUuid);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function collectResourcePageSlug(resource, options, pageSlugsByUuid, slugPrefix) {
|
|
56
|
+
const slug = cleanWebsitePageSlug(resource.slug);
|
|
57
|
+
if (slug == null) throw new Error(`Slug not found for page (${formatXMLWebsiteResourceMetadata(resource)})`, { cause: resource });
|
|
58
|
+
const pageSlug = prefixSlug(slug, slugPrefix);
|
|
59
|
+
pageSlugsByUuid.set(resource.uuid, pageSlug);
|
|
60
|
+
collectWebsitePageSlugs(resource.resource, options, slugPrefix == null ? void 0 : pageSlug, pageSlugsByUuid, pageSlug);
|
|
61
|
+
}
|
|
48
62
|
function collectWebsitePageSlugs(resources, options, slugPrefix, pageSlugsByUuid = /* @__PURE__ */ new Map(), segmentSlugPrefix = slugPrefix) {
|
|
49
63
|
const slugResources = resources ?? [];
|
|
50
64
|
for (const resource of slugResources) {
|
|
51
65
|
if ("segments" in resource) {
|
|
52
|
-
|
|
53
|
-
const segmentSlug = tree.identification.abbreviation == null ? null : parseStringContent(tree.identification.abbreviation, options);
|
|
54
|
-
if (segmentSlug == null) throw new Error(`Slug not found for segment website (website uuid “${tree.uuid}”)`, { cause: tree });
|
|
55
|
-
collectWebsitePageSlugs(tree.items?.resource, options, prefixSlug(segmentSlug, segmentSlugPrefix), pageSlugsByUuid);
|
|
56
|
-
}
|
|
66
|
+
collectSegmentPageSlugs(resource.segments.tree, options, pageSlugsByUuid, segmentSlugPrefix);
|
|
57
67
|
continue;
|
|
58
68
|
}
|
|
59
69
|
if (!("identification" in resource)) {
|
|
60
70
|
collectWebsitePageSlugs(resource.resource, options, slugPrefix, pageSlugsByUuid, segmentSlugPrefix);
|
|
61
71
|
continue;
|
|
62
72
|
}
|
|
63
|
-
const resourceProperties =
|
|
73
|
+
const resourceProperties = parseSimplifiedProperties(resource.properties, options);
|
|
64
74
|
if (websitePresentationReader(resourceProperties).value("presentation") === "page") {
|
|
65
|
-
|
|
66
|
-
if (slug == null) throw new Error(`Slug not found for page (${formatXMLWebsiteResourceMetadata(resource)})`, { cause: resource });
|
|
67
|
-
const pageSlug = prefixSlug(slug, slugPrefix);
|
|
68
|
-
pageSlugsByUuid.set(resource.uuid, pageSlug);
|
|
69
|
-
collectWebsitePageSlugs(resource.resource, options, slugPrefix == null ? void 0 : pageSlug, pageSlugsByUuid, pageSlug);
|
|
75
|
+
collectResourcePageSlug(resource, options, pageSlugsByUuid, slugPrefix);
|
|
70
76
|
continue;
|
|
71
77
|
}
|
|
72
78
|
collectWebsitePageSlugs(resource.resource, options, slugPrefix, pageSlugsByUuid, segmentSlugPrefix);
|
|
@@ -315,6 +321,450 @@ function parseCollectionDisplayedProperties(reader) {
|
|
|
315
321
|
label: value.label
|
|
316
322
|
}));
|
|
317
323
|
}
|
|
324
|
+
function readStringOrNumber(reader, label) {
|
|
325
|
+
const value = reader.value(label);
|
|
326
|
+
if (typeof value === "string") return value;
|
|
327
|
+
if (typeof value === "number") return value.toString();
|
|
328
|
+
return null;
|
|
329
|
+
}
|
|
330
|
+
function parse3dViewerComponent(parameters) {
|
|
331
|
+
const { componentProperty, componentReader, elementResource, websiteLinks } = parameters;
|
|
332
|
+
const resourceLink = findWebsiteLink(websiteLinks, "resource", (link) => link.fileFormat === "model/obj");
|
|
333
|
+
if (resourceLink == null) throw new Error(formatComponentError("Resource link not found", "3d-viewer", elementResource), { cause: componentProperty });
|
|
334
|
+
const isInteractive = componentReader.valueOr("is-interactive", true);
|
|
335
|
+
const isControlsDisplayed = componentReader.valueOr("controls-displayed", true);
|
|
336
|
+
return {
|
|
337
|
+
component: "3d-viewer",
|
|
338
|
+
linkUuid: resourceLink.uuid,
|
|
339
|
+
fileSize: resourceLink.fileSize,
|
|
340
|
+
isInteractive,
|
|
341
|
+
isControlsDisplayed
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
function parseAdvancedSearchComponent(parameters) {
|
|
345
|
+
const { componentProperty, componentReader, elementResource, options, context } = parameters;
|
|
346
|
+
const boundElementPropertyUuid = componentReader.uuid("bound-element");
|
|
347
|
+
const href = parseWebsiteLinkTarget(componentReader.valueNode("link-to"), context);
|
|
348
|
+
if (boundElementPropertyUuid == null && href == null) throw new Error(formatComponentError("Bound element or href not found", "advanced-search", elementResource), { cause: componentProperty });
|
|
349
|
+
return {
|
|
350
|
+
component: "advanced-search",
|
|
351
|
+
boundElementUuid: boundElementPropertyUuid,
|
|
352
|
+
href,
|
|
353
|
+
options: parseWebsiteOptions(elementResource.options, options)
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
function parseAnnotatedDocumentComponent(parameters) {
|
|
357
|
+
const { componentProperty, elementResource, websiteLinks } = parameters;
|
|
358
|
+
const documentLink = findWebsiteLink(websiteLinks, "resource", (link) => link.type === "internalDocument");
|
|
359
|
+
if (documentLink == null) throw new Error(formatComponentError("Document link not found", "annotated-document", elementResource), { cause: componentProperty });
|
|
360
|
+
return {
|
|
361
|
+
component: "annotated-document",
|
|
362
|
+
linkUuid: documentLink.uuid
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
function parseAnnotatedImageComponent(parameters) {
|
|
366
|
+
const { componentProperty, componentReader, elementResource, websiteLinks } = parameters;
|
|
367
|
+
const imageLinks = getWebsiteLinks(websiteLinks, "resource").filter((link) => link.type === "image" || link.type === "IIIF");
|
|
368
|
+
if (imageLinks.length === 0) throw new Error(formatComponentError("Image link not found", "annotated-image", elementResource), { cause: componentProperty });
|
|
369
|
+
const isFilterInputDisplayed = componentReader.valueOr("filter-input-displayed", true);
|
|
370
|
+
const isOptionsDisplayed = componentReader.valueOr("options-displayed", true);
|
|
371
|
+
const isAnnotationHighlightsDisplayed = componentReader.valueOr("annotation-highlights-displayed", true);
|
|
372
|
+
const isAnnotationTooltipsDisplayed = componentReader.valueOr("annotation-tooltips-displayed", true);
|
|
373
|
+
return {
|
|
374
|
+
component: "annotated-image",
|
|
375
|
+
linkUuid: imageLinks[0].uuid,
|
|
376
|
+
isFilterInputDisplayed,
|
|
377
|
+
isOptionsDisplayed,
|
|
378
|
+
isAnnotationHighlightsDisplayed,
|
|
379
|
+
isAnnotationTooltipsDisplayed
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
function parseAudioPlayerComponent(parameters) {
|
|
383
|
+
const { componentProperty, componentReader, elementResource, websiteLinks } = parameters;
|
|
384
|
+
const audioLink = findWebsiteLink(websiteLinks, "resource", (link) => link.type === "audio");
|
|
385
|
+
if (audioLink == null) throw new Error(formatComponentError("Audio link not found", "audio-player", elementResource), { cause: componentProperty });
|
|
386
|
+
const isSpeedControlsDisplayed = componentReader.valueOr("speed-controls-displayed", true);
|
|
387
|
+
const isVolumeControlsDisplayed = componentReader.valueOr("volume-controls-displayed", true);
|
|
388
|
+
const isSeekBarDisplayed = componentReader.valueOr("seek-bar-displayed", true);
|
|
389
|
+
return {
|
|
390
|
+
component: "audio-player",
|
|
391
|
+
linkUuid: audioLink.uuid,
|
|
392
|
+
isSpeedControlsDisplayed,
|
|
393
|
+
isVolumeControlsDisplayed,
|
|
394
|
+
isSeekBarDisplayed
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
function parseBibliographyComponent(parameters) {
|
|
398
|
+
const { componentProperty, componentReader, elementResource, websiteLinks, options } = parameters;
|
|
399
|
+
const bibliographies = parseBibliographyList(elementResource.bibliographies, options);
|
|
400
|
+
if (websiteLinks.length === 0 && bibliographies.length === 0) throw new Error(formatComponentError("No links found", "bibliography", elementResource), { cause: componentProperty });
|
|
401
|
+
const layout = componentReader.valueOr("layout", "long");
|
|
402
|
+
const isSourceDocumentDisplayed = componentReader.valueOr("source-document-displayed", true);
|
|
403
|
+
return {
|
|
404
|
+
component: "bibliography",
|
|
405
|
+
linkUuids: websiteLinks.map((link) => link.uuid),
|
|
406
|
+
bibliographies,
|
|
407
|
+
layout,
|
|
408
|
+
isSourceDocumentDisplayed
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
function parseButtonComponent(parameters) {
|
|
412
|
+
const { componentProperty, componentReader, elementResource, websiteLinks, options, context } = parameters;
|
|
413
|
+
const variant = componentReader.valueOr("variant", "default");
|
|
414
|
+
let isExternal = false;
|
|
415
|
+
let isRelative = false;
|
|
416
|
+
let href = parseWebsiteLinkTarget(componentReader.valueNode("navigate-to"), context);
|
|
417
|
+
if (href === null) {
|
|
418
|
+
href = parseWebsiteLinkTarget(componentReader.valueNode("link-to"), context);
|
|
419
|
+
if (href === null) throw new Error(formatComponentError("Properties “navigate-to” or “link-to” not found", "button", elementResource), { cause: componentProperty });
|
|
420
|
+
isExternal = href.startsWith("http");
|
|
421
|
+
isRelative = !href.startsWith("/");
|
|
422
|
+
}
|
|
423
|
+
const startIcon = componentReader.value("start-icon");
|
|
424
|
+
const endIcon = componentReader.value("end-icon");
|
|
425
|
+
let image = null;
|
|
426
|
+
const imageLink = findWebsiteLink(websiteLinks, "resource", (link) => link.type === "image" || link.type === "IIIF");
|
|
427
|
+
if (imageLink != null) image = {
|
|
428
|
+
uuid: imageLink.uuid,
|
|
429
|
+
label: imageLink.identification.label,
|
|
430
|
+
width: imageLink.image?.width ?? 0,
|
|
431
|
+
height: imageLink.image?.height ?? 0,
|
|
432
|
+
description: imageLink.description,
|
|
433
|
+
quality: "high"
|
|
434
|
+
};
|
|
435
|
+
const childResources = normalizeWebsiteResources(elementResource.resource);
|
|
436
|
+
const elements = [];
|
|
437
|
+
for (const childResource of childResources) {
|
|
438
|
+
const childReader = websitePresentationReader(parseSimplifiedProperties(childResource.properties, options));
|
|
439
|
+
if (childReader.value("presentation") !== "element") continue;
|
|
440
|
+
if (childReader.nestedByValue("presentation", "element").value("component") === "button") continue;
|
|
441
|
+
elements.push(parseWebElement(childResource, options, context));
|
|
442
|
+
}
|
|
443
|
+
return {
|
|
444
|
+
component: "button",
|
|
445
|
+
variant,
|
|
446
|
+
href,
|
|
447
|
+
isExternal,
|
|
448
|
+
isRelative,
|
|
449
|
+
label: elementResource.document && "content" in elementResource.document ? parseXMLContent(elementResource.document, options) : null,
|
|
450
|
+
startIcon,
|
|
451
|
+
endIcon,
|
|
452
|
+
image,
|
|
453
|
+
elements
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
function parseCollectionComponent(parameters) {
|
|
457
|
+
const { componentProperty, componentReader, elementResource, websiteLinks, options } = parameters;
|
|
458
|
+
const setLinks = getWebsiteLinks(websiteLinks, "set");
|
|
459
|
+
if (setLinks.length === 0) throw new Error(formatComponentError("Set links not found", "collection", elementResource), { cause: componentProperty });
|
|
460
|
+
const isFilterResultsBarDisplayed = componentReader.valueOr("filter-results-bar-displayed", false);
|
|
461
|
+
const isFilterInputDisplayed = componentReader.valueOr("filter-input-displayed", false);
|
|
462
|
+
const isFilterLimitedToInputFilter = componentReader.valueOr("filter-limit-to-input-filter", false);
|
|
463
|
+
const isFilterLimitedToLeafPropertyValues = componentReader.valueOr("filter-limit-to-leaf-property-values", false);
|
|
464
|
+
const isFilterSidebarDisplayed = componentReader.valueOr("filter-sidebar-displayed", false);
|
|
465
|
+
const filterSidebarSort = componentReader.valueOr("filter-sidebar-sort", "default");
|
|
466
|
+
const isFilterSidebarHelpTooltipsDisplayed = componentReader.valueOr("filter-sidebar-help-tooltips-displayed", false);
|
|
467
|
+
const componentOptions = parseWebsiteOptions(elementResource.options, options);
|
|
468
|
+
const propertyOverrides = parseCollectionPropertyOverrides(componentReader);
|
|
469
|
+
return {
|
|
470
|
+
component: "collection",
|
|
471
|
+
linkUuids: setLinks.map((link) => link.uuid),
|
|
472
|
+
displayedProperties: parseCollectionDisplayedProperties(componentReader),
|
|
473
|
+
...COLLECTION_PROPERTY_DEFAULTS,
|
|
474
|
+
...propertyOverrides,
|
|
475
|
+
image: {
|
|
476
|
+
...COLLECTION_IMAGE_DEFAULTS,
|
|
477
|
+
...propertyOverrides.image
|
|
478
|
+
},
|
|
479
|
+
filter: {
|
|
480
|
+
isSidebarDisplayed: isFilterSidebarDisplayed,
|
|
481
|
+
isResultsBarDisplayed: isFilterResultsBarDisplayed,
|
|
482
|
+
isInputDisplayed: isFilterInputDisplayed,
|
|
483
|
+
isLimitedToInputFilter: isFilterLimitedToInputFilter,
|
|
484
|
+
isLimitedToLeafPropertyValues: isFilterLimitedToLeafPropertyValues,
|
|
485
|
+
sidebarSort: filterSidebarSort,
|
|
486
|
+
isSidebarHelpTooltipsDisplayed: isFilterSidebarHelpTooltipsDisplayed
|
|
487
|
+
},
|
|
488
|
+
options: componentOptions
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
function parseEmptySpaceComponent(parameters) {
|
|
492
|
+
const { componentReader } = parameters;
|
|
493
|
+
return {
|
|
494
|
+
component: "empty-space",
|
|
495
|
+
height: componentReader.stringValue("height"),
|
|
496
|
+
width: componentReader.stringValue("width")
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
function parseEntriesComponent(parameters) {
|
|
500
|
+
const { componentProperty, componentReader, elementResource, websiteLinks } = parameters;
|
|
501
|
+
const entriesLink = findWebsiteLinkByCategories(websiteLinks, ["set", "tree"]);
|
|
502
|
+
if (entriesLink == null) throw new Error(formatComponentError("Entries link not found", "entries", elementResource), { cause: componentProperty });
|
|
503
|
+
const variant = componentReader.valueOr("variant", "entry");
|
|
504
|
+
const isFilterInputDisplayed = componentReader.valueOr("filter-input-displayed", false);
|
|
505
|
+
return {
|
|
506
|
+
component: "entries",
|
|
507
|
+
linkUuid: entriesLink.uuid,
|
|
508
|
+
variant,
|
|
509
|
+
isFilterInputDisplayed
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
function parseIframeComponent(parameters) {
|
|
513
|
+
const { componentProperty, componentReader, elementResource, websiteLinks } = parameters;
|
|
514
|
+
const webpageLink = findWebsiteLink(websiteLinks, "resource", (link) => link.type === "webpage");
|
|
515
|
+
if (webpageLink?.href == null) throw new Error(formatComponentError("URL not found", "iframe", elementResource), { cause: componentProperty });
|
|
516
|
+
return {
|
|
517
|
+
component: "iframe",
|
|
518
|
+
href: transformPermanentIdentificationUrlToItemLink(webpageLink.href),
|
|
519
|
+
height: componentReader.stringValue("height"),
|
|
520
|
+
width: componentReader.stringValue("width")
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
function parseIiifViewerComponent(parameters) {
|
|
524
|
+
const { componentProperty, componentReader, elementResource, websiteLinks } = parameters;
|
|
525
|
+
const manifestLink = findWebsiteLink(websiteLinks, "resource", (link) => link.type === "IIIF");
|
|
526
|
+
if (manifestLink == null) throw new Error(formatComponentError("Manifest link not found", "iiif-viewer", elementResource), { cause: componentProperty });
|
|
527
|
+
const variant = componentReader.valueOr("variant", "universal-viewer");
|
|
528
|
+
return {
|
|
529
|
+
component: "iiif-viewer",
|
|
530
|
+
linkUuid: manifestLink.uuid,
|
|
531
|
+
variant
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
function parseImageComponent(parameters) {
|
|
535
|
+
const { componentProperty, componentReader, elementResource, websiteLinks } = parameters;
|
|
536
|
+
if (websiteLinks.length === 0) throw new Error(formatComponentError("No links found", "image", elementResource), { cause: componentProperty });
|
|
537
|
+
const imageQuality = componentReader.valueOr("image-quality", "high");
|
|
538
|
+
const images = Array.from(websiteLinks, (link) => ({
|
|
539
|
+
uuid: link.uuid,
|
|
540
|
+
label: link.identification.label,
|
|
541
|
+
width: "image" in link ? link.image?.width ?? 0 : 0,
|
|
542
|
+
height: "image" in link ? link.image?.height ?? 0 : 0,
|
|
543
|
+
description: link.description,
|
|
544
|
+
quality: imageQuality
|
|
545
|
+
}));
|
|
546
|
+
const variant = componentReader.valueOr("variant", "default");
|
|
547
|
+
const captionLayout = componentReader.valueOr("layout-caption", "bottom");
|
|
548
|
+
const width = readStringOrNumber(componentReader, "width");
|
|
549
|
+
const height = readStringOrNumber(componentReader, "height");
|
|
550
|
+
const isFullWidth = componentReader.valueOr("is-full-width", true);
|
|
551
|
+
const isFullHeight = componentReader.valueOr("is-full-height", true);
|
|
552
|
+
const captionSource = componentReader.valueOr("source-caption", "name");
|
|
553
|
+
const altTextSource = componentReader.valueOr("alt-text-source", "name");
|
|
554
|
+
const isTransparentBackground = componentReader.valueOr("is-transparent", false);
|
|
555
|
+
const isCover = componentReader.valueOr("is-cover", false);
|
|
556
|
+
const variantReader = componentReader.nested("variant");
|
|
557
|
+
let carouselOptions = null;
|
|
558
|
+
if (images.length > 1) {
|
|
559
|
+
const secondsPerImage = variant === "carousel" ? readStringOrNumber(variantReader, "seconds-per-image") : null;
|
|
560
|
+
carouselOptions = { secondsPerImage: Number(secondsPerImage ?? 5) };
|
|
561
|
+
}
|
|
562
|
+
let heroOptions = null;
|
|
563
|
+
if (variant === "hero") heroOptions = {
|
|
564
|
+
isBackgroundImageDisplayed: variantReader.valueOr("background-image-displayed", true),
|
|
565
|
+
isDocumentDisplayed: variantReader.valueOr("document-displayed", true)
|
|
566
|
+
};
|
|
567
|
+
return {
|
|
568
|
+
component: "image",
|
|
569
|
+
images,
|
|
570
|
+
variant,
|
|
571
|
+
width,
|
|
572
|
+
height,
|
|
573
|
+
isFullWidth,
|
|
574
|
+
isFullHeight,
|
|
575
|
+
imageQuality,
|
|
576
|
+
captionLayout,
|
|
577
|
+
captionSource,
|
|
578
|
+
altTextSource,
|
|
579
|
+
isTransparentBackground,
|
|
580
|
+
isCover,
|
|
581
|
+
carouselOptions,
|
|
582
|
+
heroOptions
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
function parseImageGalleryComponent(parameters) {
|
|
586
|
+
const { componentProperty, componentReader, elementResource, websiteLinks } = parameters;
|
|
587
|
+
const galleryLink = findWebsiteLinkByCategories(websiteLinks, ["set", "tree"]);
|
|
588
|
+
if (galleryLink == null) throw new Error(formatComponentError("Image gallery link not found", "image-gallery", elementResource), { cause: componentProperty });
|
|
589
|
+
const isFilterInputDisplayed = componentReader.valueOr("filter-input-displayed", true);
|
|
590
|
+
return {
|
|
591
|
+
component: "image-gallery",
|
|
592
|
+
linkUuid: galleryLink.uuid,
|
|
593
|
+
isFilterInputDisplayed
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
function parseMapComponent(parameters) {
|
|
597
|
+
const { componentProperty, componentReader, elementResource, websiteLinks } = parameters;
|
|
598
|
+
const mapLink = findWebsiteLinkByCategories(websiteLinks, ["set", "tree"]);
|
|
599
|
+
if (mapLink == null) throw new Error(formatComponentError("Map link not found", "map", elementResource), { cause: componentProperty });
|
|
600
|
+
const isInteractive = componentReader.valueOr("is-interactive", true);
|
|
601
|
+
const isClustered = componentReader.valueOr("is-clustered", false);
|
|
602
|
+
const isUsingPins = componentReader.valueOr("is-using-pins", false);
|
|
603
|
+
const customBasemap = componentReader.value("custom-basemap");
|
|
604
|
+
let initialBounds = null;
|
|
605
|
+
const initialBoundsProperty = componentReader.value("initial-bounds");
|
|
606
|
+
if (initialBoundsProperty !== null) initialBounds = parseBounds(String(initialBoundsProperty));
|
|
607
|
+
let maximumBounds = null;
|
|
608
|
+
const maximumBoundsProperty = componentReader.value("maximum-bounds");
|
|
609
|
+
if (maximumBoundsProperty !== null) maximumBounds = parseBounds(String(maximumBoundsProperty));
|
|
610
|
+
const isControlsDisplayed = componentReader.valueOr("controls-displayed", false);
|
|
611
|
+
const isFullHeight = componentReader.valueOr("is-full-height", false);
|
|
612
|
+
return {
|
|
613
|
+
component: "map",
|
|
614
|
+
linkUuid: mapLink.uuid,
|
|
615
|
+
customBasemap,
|
|
616
|
+
initialBounds,
|
|
617
|
+
maximumBounds,
|
|
618
|
+
isInteractive,
|
|
619
|
+
isClustered,
|
|
620
|
+
isUsingPins,
|
|
621
|
+
isControlsDisplayed,
|
|
622
|
+
isFullHeight
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
function parseQueryItemQueries(propertyVariables, queryLanguage, elementResource) {
|
|
626
|
+
const queries = [];
|
|
627
|
+
for (const propertyVariable of propertyVariables) {
|
|
628
|
+
if (propertyVariable.uuid === null) throw new Error(formatComponentError("Property variable UUID not found", "query", elementResource), { cause: propertyVariable });
|
|
629
|
+
const dataType = propertyVariable.dataType;
|
|
630
|
+
if (dataType === "coordinate") throw new Error(formatComponentError("Query prompts with data type \"coordinate\" are not supported", "query", elementResource), { cause: propertyVariable });
|
|
631
|
+
queries.push({
|
|
632
|
+
target: "property",
|
|
633
|
+
propertyVariable: propertyVariable.uuid,
|
|
634
|
+
dataType,
|
|
635
|
+
matchMode: "exact",
|
|
636
|
+
isCaseSensitive: true,
|
|
637
|
+
language: queryLanguage
|
|
638
|
+
});
|
|
639
|
+
}
|
|
640
|
+
return queries;
|
|
641
|
+
}
|
|
642
|
+
function parseQueryComponent(parameters) {
|
|
643
|
+
const { componentProperty, componentReader, elementResource, websiteLinks, options } = parameters;
|
|
644
|
+
const setLinks = getWebsiteLinks(websiteLinks, "set");
|
|
645
|
+
if (setLinks.length === 0) throw new Error(formatComponentError("Set links not found", "query", elementResource), { cause: componentProperty });
|
|
646
|
+
if (componentProperty.properties.length === 0) throw new Error(formatComponentError("Query properties not found", "query", elementResource), { cause: componentProperty });
|
|
647
|
+
const items = [];
|
|
648
|
+
for (const queryItem of componentProperty.properties) {
|
|
649
|
+
const queryReader = websitePresentationReader(queryItem.properties);
|
|
650
|
+
const label = queryReader.multilingualValue("query-prompt", options);
|
|
651
|
+
if (label === null) continue;
|
|
652
|
+
const propertyVariables = queryReader.values("use-property").filter((value) => value.uuid !== null);
|
|
653
|
+
const queryLanguage = options.languages[0];
|
|
654
|
+
if (queryLanguage == null) throw new Error(formatComponentError("Query language not found", "query", elementResource));
|
|
655
|
+
const queries = parseQueryItemQueries(propertyVariables, queryLanguage, elementResource);
|
|
656
|
+
const startIcon = queryReader.value("start-icon");
|
|
657
|
+
const endIcon = queryReader.value("end-icon");
|
|
658
|
+
items.push({
|
|
659
|
+
label,
|
|
660
|
+
queries,
|
|
661
|
+
startIcon,
|
|
662
|
+
endIcon
|
|
663
|
+
});
|
|
664
|
+
}
|
|
665
|
+
if (items.length === 0) throw new Error(formatComponentError("No queries found", "query", elementResource), { cause: componentProperty });
|
|
666
|
+
const componentOptions = parseWebsiteOptions(elementResource.options, options);
|
|
667
|
+
const collectionProperties = parseCollectionPropertyOverrides(componentReader.nestedByValue("sub-component-override", "collection"));
|
|
668
|
+
const displayedProperties = parseCollectionDisplayedProperties(componentReader);
|
|
669
|
+
if (displayedProperties != null) collectionProperties.displayedProperties = displayedProperties;
|
|
670
|
+
return {
|
|
671
|
+
component: "query",
|
|
672
|
+
linkUuids: setLinks.map((link) => link.uuid),
|
|
673
|
+
items,
|
|
674
|
+
options: componentOptions,
|
|
675
|
+
collectionProperties
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
function parseTableComponent(parameters) {
|
|
679
|
+
const { componentProperty, elementResource, websiteLinks } = parameters;
|
|
680
|
+
const tableLink = findWebsiteLink(websiteLinks, "set");
|
|
681
|
+
if (tableLink == null) throw new Error(formatComponentError("Table link not found", "table", elementResource), { cause: componentProperty });
|
|
682
|
+
return {
|
|
683
|
+
component: "table",
|
|
684
|
+
linkUuid: tableLink.uuid
|
|
685
|
+
};
|
|
686
|
+
}
|
|
687
|
+
function parseSearchBarComponent(parameters) {
|
|
688
|
+
const { componentProperty, componentReader, elementResource, options, context } = parameters;
|
|
689
|
+
const queryVariant = componentReader.valueOr("query-variant", "submit");
|
|
690
|
+
const boundElementUuid = componentReader.uuid("bound-element");
|
|
691
|
+
const href = parseWebsiteLinkTarget(componentReader.valueNode("link-to"), context);
|
|
692
|
+
if (boundElementUuid === null && href === null) throw new Error(formatComponentError("Bound element or href not found", "search-bar", elementResource), { cause: componentProperty });
|
|
693
|
+
return {
|
|
694
|
+
component: "search-bar",
|
|
695
|
+
queryVariant,
|
|
696
|
+
placeholder: componentReader.multilingualValue("placeholder-text", options),
|
|
697
|
+
baseFilterQueries: componentReader.value("base-filter-queries")?.replaceAll(String.raw`\{`, "{").replaceAll(String.raw`\}`, "}") ?? null,
|
|
698
|
+
boundElementUuid,
|
|
699
|
+
href
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
function parseTextComponent(parameters) {
|
|
703
|
+
const { componentProperty, componentReader, elementResource, options } = parameters;
|
|
704
|
+
const content = elementResource.document && "content" in elementResource.document ? parseXMLContent(elementResource.document, options) : null;
|
|
705
|
+
if (content == null) throw new Error(formatComponentError("Content not found", "text", elementResource), { cause: componentProperty });
|
|
706
|
+
let variantName = "block";
|
|
707
|
+
let variant;
|
|
708
|
+
const variantProperty = componentReader.property("variant");
|
|
709
|
+
if (variantProperty !== null) {
|
|
710
|
+
const variantReader = websitePresentationReader(variantProperty.properties);
|
|
711
|
+
variantName = variantProperty.values[0].content;
|
|
712
|
+
switch (variantName) {
|
|
713
|
+
case "paragraph":
|
|
714
|
+
variant = {
|
|
715
|
+
name: variantName,
|
|
716
|
+
size: variantReader.valueOr("size", "md")
|
|
717
|
+
};
|
|
718
|
+
break;
|
|
719
|
+
case "label":
|
|
720
|
+
variant = {
|
|
721
|
+
name: variantName,
|
|
722
|
+
size: variantReader.valueOr("size", "md")
|
|
723
|
+
};
|
|
724
|
+
break;
|
|
725
|
+
case "heading":
|
|
726
|
+
variant = {
|
|
727
|
+
name: variantName,
|
|
728
|
+
size: variantReader.valueOr("size", "md")
|
|
729
|
+
};
|
|
730
|
+
break;
|
|
731
|
+
case "display":
|
|
732
|
+
variant = {
|
|
733
|
+
name: variantName,
|
|
734
|
+
size: variantReader.valueOr("size", "md")
|
|
735
|
+
};
|
|
736
|
+
break;
|
|
737
|
+
default: variant = { name: variantName };
|
|
738
|
+
}
|
|
739
|
+
} else variant = { name: variantName };
|
|
740
|
+
const headingLevel = componentReader.value("heading-level");
|
|
741
|
+
return {
|
|
742
|
+
component: "text",
|
|
743
|
+
variant,
|
|
744
|
+
headingLevel,
|
|
745
|
+
content
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
function parseTimelineComponent(parameters) {
|
|
749
|
+
const { componentProperty, elementResource, websiteLinks } = parameters;
|
|
750
|
+
const timelineLink = findWebsiteLink(websiteLinks, "tree");
|
|
751
|
+
if (timelineLink == null) throw new Error(formatComponentError("Timeline link not found", "timeline", elementResource), { cause: componentProperty });
|
|
752
|
+
return {
|
|
753
|
+
component: "timeline",
|
|
754
|
+
linkUuid: timelineLink.uuid
|
|
755
|
+
};
|
|
756
|
+
}
|
|
757
|
+
function parseVideoComponent(parameters) {
|
|
758
|
+
const { componentProperty, componentReader, elementResource, websiteLinks } = parameters;
|
|
759
|
+
const videoLink = findWebsiteLink(websiteLinks, "resource", (link) => link.type === "video");
|
|
760
|
+
if (videoLink == null) throw new Error(formatComponentError("Video link not found", "video", elementResource), { cause: componentProperty });
|
|
761
|
+
const isChaptersDisplayed = componentReader.valueOr("chapters-displayed", true);
|
|
762
|
+
return {
|
|
763
|
+
component: "video",
|
|
764
|
+
linkUuid: videoLink.uuid,
|
|
765
|
+
isChaptersDisplayed
|
|
766
|
+
};
|
|
767
|
+
}
|
|
318
768
|
/**
|
|
319
769
|
* Parses raw web element properties into a standardized WebElementComponent structure
|
|
320
770
|
*
|
|
@@ -326,461 +776,39 @@ function parseWebElementProperties(componentProperty, elementResource, options,
|
|
|
326
776
|
const unparsedComponentName = componentProperty.values[0].content;
|
|
327
777
|
const componentNameResult = v.safeParse(componentSchema, unparsedComponentName);
|
|
328
778
|
const componentName = componentNameResult.success ? componentNameResult.output : void 0;
|
|
329
|
-
let properties = null;
|
|
330
779
|
const websiteLinks = parseLinks(elementResource.links, options);
|
|
331
|
-
const
|
|
780
|
+
const parameters = {
|
|
781
|
+
componentProperty,
|
|
782
|
+
componentReader: websitePresentationReader(componentProperty.properties),
|
|
783
|
+
elementResource,
|
|
784
|
+
websiteLinks,
|
|
785
|
+
options,
|
|
786
|
+
context
|
|
787
|
+
};
|
|
332
788
|
switch (componentName) {
|
|
333
|
-
case "3d-viewer":
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
case "
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
href,
|
|
355
|
-
options: parseWebsiteOptions(elementResource.options, options)
|
|
356
|
-
};
|
|
357
|
-
break;
|
|
358
|
-
}
|
|
359
|
-
case "annotated-document": {
|
|
360
|
-
const documentLink = findWebsiteLink(websiteLinks, "resource", (link) => link.type === "internalDocument");
|
|
361
|
-
if (documentLink == null) throw new Error(formatComponentError("Document link not found", componentName, elementResource), { cause: componentProperty });
|
|
362
|
-
properties = {
|
|
363
|
-
component: "annotated-document",
|
|
364
|
-
linkUuid: documentLink.uuid
|
|
365
|
-
};
|
|
366
|
-
break;
|
|
367
|
-
}
|
|
368
|
-
case "annotated-image": {
|
|
369
|
-
const imageLinks = getWebsiteLinks(websiteLinks, "resource").filter((link) => link.type === "image" || link.type === "IIIF");
|
|
370
|
-
if (imageLinks.length === 0) throw new Error(formatComponentError("Image link not found", componentName, elementResource), { cause: componentProperty });
|
|
371
|
-
const isFilterInputDisplayed = componentReader.valueOr("filter-input-displayed", true);
|
|
372
|
-
const isOptionsDisplayed = componentReader.valueOr("options-displayed", true);
|
|
373
|
-
const isAnnotationHighlightsDisplayed = componentReader.valueOr("annotation-highlights-displayed", true);
|
|
374
|
-
const isAnnotationTooltipsDisplayed = componentReader.valueOr("annotation-tooltips-displayed", true);
|
|
375
|
-
properties = {
|
|
376
|
-
component: "annotated-image",
|
|
377
|
-
linkUuid: imageLinks[0].uuid,
|
|
378
|
-
isFilterInputDisplayed,
|
|
379
|
-
isOptionsDisplayed,
|
|
380
|
-
isAnnotationHighlightsDisplayed,
|
|
381
|
-
isAnnotationTooltipsDisplayed
|
|
382
|
-
};
|
|
383
|
-
break;
|
|
384
|
-
}
|
|
385
|
-
case "audio-player": {
|
|
386
|
-
const audioLink = findWebsiteLink(websiteLinks, "resource", (link) => link.type === "audio");
|
|
387
|
-
if (audioLink == null) throw new Error(formatComponentError("Audio link not found", componentName, elementResource), { cause: componentProperty });
|
|
388
|
-
const isSpeedControlsDisplayed = componentReader.valueOr("speed-controls-displayed", true);
|
|
389
|
-
const isVolumeControlsDisplayed = componentReader.valueOr("volume-controls-displayed", true);
|
|
390
|
-
const isSeekBarDisplayed = componentReader.valueOr("seek-bar-displayed", true);
|
|
391
|
-
properties = {
|
|
392
|
-
component: "audio-player",
|
|
393
|
-
linkUuid: audioLink.uuid,
|
|
394
|
-
isSpeedControlsDisplayed,
|
|
395
|
-
isVolumeControlsDisplayed,
|
|
396
|
-
isSeekBarDisplayed
|
|
397
|
-
};
|
|
398
|
-
break;
|
|
399
|
-
}
|
|
400
|
-
case "bibliography": {
|
|
401
|
-
const bibliographies = parseBibliographyList(elementResource.bibliographies, options);
|
|
402
|
-
if (websiteLinks.length === 0 && bibliographies.length === 0) throw new Error(formatComponentError("No links found", componentName, elementResource), { cause: componentProperty });
|
|
403
|
-
const layout = componentReader.valueOr("layout", "long");
|
|
404
|
-
const isSourceDocumentDisplayed = componentReader.valueOr("source-document-displayed", true);
|
|
405
|
-
properties = {
|
|
406
|
-
component: "bibliography",
|
|
407
|
-
linkUuids: websiteLinks.map((link) => link.uuid),
|
|
408
|
-
bibliographies,
|
|
409
|
-
layout,
|
|
410
|
-
isSourceDocumentDisplayed
|
|
411
|
-
};
|
|
412
|
-
break;
|
|
413
|
-
}
|
|
414
|
-
case "button": {
|
|
415
|
-
const variant = componentReader.valueOr("variant", "default");
|
|
416
|
-
let isExternal = false;
|
|
417
|
-
let isRelative = false;
|
|
418
|
-
let href = parseWebsiteLinkTarget(componentReader.valueNode("navigate-to"), context);
|
|
419
|
-
if (href === null) {
|
|
420
|
-
href = parseWebsiteLinkTarget(componentReader.valueNode("link-to"), context);
|
|
421
|
-
if (href === null) throw new Error(formatComponentError("Properties “navigate-to” or “link-to” not found", componentName, elementResource), { cause: componentProperty });
|
|
422
|
-
isExternal = href.startsWith("http");
|
|
423
|
-
isRelative = !href.startsWith("/");
|
|
424
|
-
}
|
|
425
|
-
const startIcon = componentReader.value("start-icon");
|
|
426
|
-
const endIcon = componentReader.value("end-icon");
|
|
427
|
-
let image = null;
|
|
428
|
-
const imageLink = findWebsiteLink(websiteLinks, "resource", (link) => link.type === "image" || link.type === "IIIF");
|
|
429
|
-
if (imageLink != null) image = {
|
|
430
|
-
uuid: imageLink.uuid,
|
|
431
|
-
label: imageLink.identification.label,
|
|
432
|
-
width: imageLink.image?.width ?? 0,
|
|
433
|
-
height: imageLink.image?.height ?? 0,
|
|
434
|
-
description: imageLink.description,
|
|
435
|
-
quality: "high"
|
|
436
|
-
};
|
|
437
|
-
const childResources = elementResource.resource ? normalizeWebsiteResources(elementResource.resource) : [];
|
|
438
|
-
const elements = [];
|
|
439
|
-
for (const childResource of childResources) {
|
|
440
|
-
const childReader = websitePresentationReader(childResource.properties ? parseSimplifiedProperties(childResource.properties, options) : []);
|
|
441
|
-
if (childReader.value("presentation") !== "element") continue;
|
|
442
|
-
if (childReader.nestedByValue("presentation", "element").value("component") === "button") continue;
|
|
443
|
-
elements.push(parseWebElement(childResource, options, context));
|
|
444
|
-
}
|
|
445
|
-
properties = {
|
|
446
|
-
component: "button",
|
|
447
|
-
variant,
|
|
448
|
-
href,
|
|
449
|
-
isExternal,
|
|
450
|
-
isRelative,
|
|
451
|
-
label: elementResource.document && "content" in elementResource.document ? parseXMLContent(elementResource.document, options) : null,
|
|
452
|
-
startIcon,
|
|
453
|
-
endIcon,
|
|
454
|
-
image,
|
|
455
|
-
elements
|
|
456
|
-
};
|
|
457
|
-
break;
|
|
458
|
-
}
|
|
459
|
-
case "collection": {
|
|
460
|
-
const setLinks = getWebsiteLinks(websiteLinks, "set");
|
|
461
|
-
if (setLinks.length === 0) throw new Error(formatComponentError("Set links not found", componentName, elementResource), { cause: componentProperty });
|
|
462
|
-
const isFilterResultsBarDisplayed = componentReader.valueOr("filter-results-bar-displayed", false);
|
|
463
|
-
const isFilterInputDisplayed = componentReader.valueOr("filter-input-displayed", false);
|
|
464
|
-
const isFilterLimitedToInputFilter = componentReader.valueOr("filter-limit-to-input-filter", false);
|
|
465
|
-
const isFilterLimitedToLeafPropertyValues = componentReader.valueOr("filter-limit-to-leaf-property-values", false);
|
|
466
|
-
const isFilterSidebarDisplayed = componentReader.valueOr("filter-sidebar-displayed", false);
|
|
467
|
-
const filterSidebarSort = componentReader.valueOr("filter-sidebar-sort", "default");
|
|
468
|
-
const componentOptions = parseWebsiteOptions(elementResource.options, options);
|
|
469
|
-
const propertyOverrides = parseCollectionPropertyOverrides(componentReader);
|
|
470
|
-
properties = {
|
|
471
|
-
component: "collection",
|
|
472
|
-
linkUuids: setLinks.map((link) => link.uuid),
|
|
473
|
-
displayedProperties: parseCollectionDisplayedProperties(componentReader),
|
|
474
|
-
...COLLECTION_PROPERTY_DEFAULTS,
|
|
475
|
-
...propertyOverrides,
|
|
476
|
-
image: {
|
|
477
|
-
...COLLECTION_IMAGE_DEFAULTS,
|
|
478
|
-
...propertyOverrides.image
|
|
479
|
-
},
|
|
480
|
-
filter: {
|
|
481
|
-
isSidebarDisplayed: isFilterSidebarDisplayed,
|
|
482
|
-
isResultsBarDisplayed: isFilterResultsBarDisplayed,
|
|
483
|
-
isInputDisplayed: isFilterInputDisplayed,
|
|
484
|
-
isLimitedToInputFilter: isFilterLimitedToInputFilter,
|
|
485
|
-
isLimitedToLeafPropertyValues: isFilterLimitedToLeafPropertyValues,
|
|
486
|
-
sidebarSort: filterSidebarSort
|
|
487
|
-
},
|
|
488
|
-
options: componentOptions
|
|
489
|
-
};
|
|
490
|
-
break;
|
|
491
|
-
}
|
|
492
|
-
case "empty-space":
|
|
493
|
-
properties = {
|
|
494
|
-
component: "empty-space",
|
|
495
|
-
height: componentReader.stringValue("height"),
|
|
496
|
-
width: componentReader.stringValue("width")
|
|
497
|
-
};
|
|
498
|
-
break;
|
|
499
|
-
case "entries": {
|
|
500
|
-
const entriesLink = findWebsiteLinkByCategories(websiteLinks, ["set", "tree"]);
|
|
501
|
-
if (entriesLink == null) throw new Error(formatComponentError("Entries link not found", componentName, elementResource), { cause: componentProperty });
|
|
502
|
-
const variant = componentReader.valueOr("variant", "entry");
|
|
503
|
-
const isFilterInputDisplayed = componentReader.valueOr("filter-input-displayed", false);
|
|
504
|
-
properties = {
|
|
505
|
-
component: "entries",
|
|
506
|
-
linkUuid: entriesLink.uuid,
|
|
507
|
-
variant,
|
|
508
|
-
isFilterInputDisplayed
|
|
509
|
-
};
|
|
510
|
-
break;
|
|
511
|
-
}
|
|
512
|
-
case "iframe": {
|
|
513
|
-
const webpageLink = findWebsiteLink(websiteLinks, "resource", (link) => link.type === "webpage");
|
|
514
|
-
if (webpageLink?.href == null) throw new Error(formatComponentError("URL not found", componentName, elementResource), { cause: componentProperty });
|
|
515
|
-
properties = {
|
|
516
|
-
component: "iframe",
|
|
517
|
-
href: transformPermanentIdentificationUrlToItemLink(webpageLink.href),
|
|
518
|
-
height: componentReader.stringValue("height"),
|
|
519
|
-
width: componentReader.stringValue("width")
|
|
520
|
-
};
|
|
521
|
-
break;
|
|
522
|
-
}
|
|
523
|
-
case "iiif-viewer": {
|
|
524
|
-
const manifestLink = findWebsiteLink(websiteLinks, "resource", (link) => link.type === "IIIF");
|
|
525
|
-
if (manifestLink == null) throw new Error(formatComponentError("Manifest link not found", componentName, elementResource), { cause: componentProperty });
|
|
526
|
-
const variant = componentReader.valueOr("variant", "universal-viewer");
|
|
527
|
-
properties = {
|
|
528
|
-
component: "iiif-viewer",
|
|
529
|
-
linkUuid: manifestLink.uuid,
|
|
530
|
-
variant
|
|
531
|
-
};
|
|
532
|
-
break;
|
|
533
|
-
}
|
|
534
|
-
case "image": {
|
|
535
|
-
if (websiteLinks.length === 0) throw new Error(formatComponentError("No links found", componentName, elementResource), { cause: componentProperty });
|
|
536
|
-
const imageQuality = componentReader.valueOr("image-quality", "high");
|
|
537
|
-
const images = Array.from(websiteLinks, (link) => ({
|
|
538
|
-
uuid: link.uuid,
|
|
539
|
-
label: link.identification.label,
|
|
540
|
-
width: "image" in link ? link.image?.width ?? 0 : 0,
|
|
541
|
-
height: "image" in link ? link.image?.height ?? 0 : 0,
|
|
542
|
-
description: link.description,
|
|
543
|
-
quality: imageQuality
|
|
544
|
-
}));
|
|
545
|
-
const variant = componentReader.valueOr("variant", "default");
|
|
546
|
-
const captionLayout = componentReader.valueOr("layout-caption", "bottom");
|
|
547
|
-
let width = null;
|
|
548
|
-
const widthProperty = componentReader.value("width");
|
|
549
|
-
if (widthProperty !== null) {
|
|
550
|
-
if (typeof widthProperty === "string") width = widthProperty;
|
|
551
|
-
else if (typeof widthProperty === "number") width = widthProperty.toString();
|
|
552
|
-
}
|
|
553
|
-
let height = null;
|
|
554
|
-
const heightProperty = componentReader.value("height");
|
|
555
|
-
if (heightProperty !== null) {
|
|
556
|
-
if (typeof heightProperty === "string") height = heightProperty;
|
|
557
|
-
else if (typeof heightProperty === "number") height = heightProperty.toString();
|
|
558
|
-
}
|
|
559
|
-
const isFullWidth = componentReader.valueOr("is-full-width", true);
|
|
560
|
-
const isFullHeight = componentReader.valueOr("is-full-height", true);
|
|
561
|
-
const captionSource = componentReader.valueOr("source-caption", "name");
|
|
562
|
-
const altTextSource = componentReader.valueOr("alt-text-source", "name");
|
|
563
|
-
const isTransparentBackground = componentReader.valueOr("is-transparent", false);
|
|
564
|
-
const isCover = componentReader.valueOr("is-cover", false);
|
|
565
|
-
const variantReader = componentReader.nested("variant");
|
|
566
|
-
let carouselOptions = null;
|
|
567
|
-
if (images.length > 1) {
|
|
568
|
-
let secondsPerImage = 5;
|
|
569
|
-
if (variant === "carousel") {
|
|
570
|
-
const secondsPerImageProperty = variantReader.value("seconds-per-image");
|
|
571
|
-
if (secondsPerImageProperty !== null) {
|
|
572
|
-
if (typeof secondsPerImageProperty === "number") secondsPerImage = secondsPerImageProperty;
|
|
573
|
-
else if (typeof secondsPerImageProperty === "string") secondsPerImage = Number(secondsPerImageProperty);
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
carouselOptions = { secondsPerImage };
|
|
577
|
-
}
|
|
578
|
-
let heroOptions = null;
|
|
579
|
-
if (variant === "hero") heroOptions = {
|
|
580
|
-
isBackgroundImageDisplayed: variantReader.valueOr("background-image-displayed", true),
|
|
581
|
-
isDocumentDisplayed: variantReader.valueOr("document-displayed", true)
|
|
582
|
-
};
|
|
583
|
-
properties = {
|
|
584
|
-
component: "image",
|
|
585
|
-
images,
|
|
586
|
-
variant,
|
|
587
|
-
width,
|
|
588
|
-
height,
|
|
589
|
-
isFullWidth,
|
|
590
|
-
isFullHeight,
|
|
591
|
-
imageQuality,
|
|
592
|
-
captionLayout,
|
|
593
|
-
captionSource,
|
|
594
|
-
altTextSource,
|
|
595
|
-
isTransparentBackground,
|
|
596
|
-
isCover,
|
|
597
|
-
carouselOptions,
|
|
598
|
-
heroOptions
|
|
599
|
-
};
|
|
600
|
-
break;
|
|
601
|
-
}
|
|
602
|
-
case "image-gallery": {
|
|
603
|
-
const galleryLink = findWebsiteLinkByCategories(websiteLinks, ["set", "tree"]);
|
|
604
|
-
if (galleryLink == null) throw new Error(formatComponentError("Image gallery link not found", componentName, elementResource), { cause: componentProperty });
|
|
605
|
-
const isFilterInputDisplayed = componentReader.valueOr("filter-input-displayed", true);
|
|
606
|
-
properties = {
|
|
607
|
-
component: "image-gallery",
|
|
608
|
-
linkUuid: galleryLink.uuid,
|
|
609
|
-
isFilterInputDisplayed
|
|
610
|
-
};
|
|
611
|
-
break;
|
|
612
|
-
}
|
|
613
|
-
case "map": {
|
|
614
|
-
const mapLink = findWebsiteLinkByCategories(websiteLinks, ["set", "tree"]);
|
|
615
|
-
if (mapLink == null) throw new Error(formatComponentError("Map link not found", componentName, elementResource), { cause: componentProperty });
|
|
616
|
-
const isInteractive = componentReader.valueOr("is-interactive", true);
|
|
617
|
-
const isClustered = componentReader.valueOr("is-clustered", false);
|
|
618
|
-
const isUsingPins = componentReader.valueOr("is-using-pins", false);
|
|
619
|
-
const customBasemap = componentReader.value("custom-basemap");
|
|
620
|
-
let initialBounds = null;
|
|
621
|
-
const initialBoundsProperty = componentReader.value("initial-bounds");
|
|
622
|
-
if (initialBoundsProperty !== null) initialBounds = parseBounds(String(initialBoundsProperty));
|
|
623
|
-
let maximumBounds = null;
|
|
624
|
-
const maximumBoundsProperty = componentReader.value("maximum-bounds");
|
|
625
|
-
if (maximumBoundsProperty !== null) maximumBounds = parseBounds(String(maximumBoundsProperty));
|
|
626
|
-
const isControlsDisplayed = componentReader.valueOr("controls-displayed", false);
|
|
627
|
-
const isFullHeight = componentReader.valueOr("is-full-height", false);
|
|
628
|
-
properties = {
|
|
629
|
-
component: "map",
|
|
630
|
-
linkUuid: mapLink.uuid,
|
|
631
|
-
customBasemap,
|
|
632
|
-
initialBounds,
|
|
633
|
-
maximumBounds,
|
|
634
|
-
isInteractive,
|
|
635
|
-
isClustered,
|
|
636
|
-
isUsingPins,
|
|
637
|
-
isControlsDisplayed,
|
|
638
|
-
isFullHeight
|
|
639
|
-
};
|
|
640
|
-
break;
|
|
641
|
-
}
|
|
642
|
-
case "query": {
|
|
643
|
-
const setLinks = getWebsiteLinks(websiteLinks, "set");
|
|
644
|
-
if (setLinks.length === 0) throw new Error(formatComponentError("Set links not found", componentName, elementResource), { cause: componentProperty });
|
|
645
|
-
if (componentProperty.properties.length === 0) throw new Error(formatComponentError("Query properties not found", componentName, elementResource), { cause: componentProperty });
|
|
646
|
-
const items = [];
|
|
647
|
-
for (const queryItem of componentProperty.properties) {
|
|
648
|
-
const queryReader = websitePresentationReader(queryItem.properties);
|
|
649
|
-
const label = queryReader.multilingualValue("query-prompt", options);
|
|
650
|
-
if (label === null) continue;
|
|
651
|
-
const propertyVariables = queryReader.values("use-property").filter((value) => value.uuid !== null);
|
|
652
|
-
const queryLanguage = options.languages[0];
|
|
653
|
-
if (queryLanguage == null) throw new Error(formatComponentError("Query language not found", componentName, elementResource));
|
|
654
|
-
const queries = [];
|
|
655
|
-
for (const propertyVariable of propertyVariables) {
|
|
656
|
-
if (propertyVariable.uuid === null) throw new Error(formatComponentError("Property variable UUID not found", componentName, elementResource), { cause: propertyVariable });
|
|
657
|
-
const dataType = propertyVariable.dataType;
|
|
658
|
-
if (dataType === "coordinate") throw new Error(formatComponentError("Query prompts with data type \"coordinate\" are not supported", componentName, elementResource), { cause: propertyVariable });
|
|
659
|
-
queries.push({
|
|
660
|
-
target: "property",
|
|
661
|
-
propertyVariable: propertyVariable.uuid,
|
|
662
|
-
dataType,
|
|
663
|
-
matchMode: "exact",
|
|
664
|
-
isCaseSensitive: true,
|
|
665
|
-
language: queryLanguage
|
|
666
|
-
});
|
|
667
|
-
}
|
|
668
|
-
const startIcon = queryReader.value("start-icon");
|
|
669
|
-
const endIcon = queryReader.value("end-icon");
|
|
670
|
-
items.push({
|
|
671
|
-
label,
|
|
672
|
-
queries,
|
|
673
|
-
startIcon,
|
|
674
|
-
endIcon
|
|
675
|
-
});
|
|
676
|
-
}
|
|
677
|
-
if (items.length === 0) throw new Error(formatComponentError("No queries found", componentName, elementResource), { cause: componentProperty });
|
|
678
|
-
const componentOptions = parseWebsiteOptions(elementResource.options, options);
|
|
679
|
-
const collectionProperties = parseCollectionPropertyOverrides(componentReader.nestedByValue("sub-component-override", "collection"));
|
|
680
|
-
const displayedProperties = parseCollectionDisplayedProperties(componentReader);
|
|
681
|
-
if (displayedProperties != null) collectionProperties.displayedProperties = displayedProperties;
|
|
682
|
-
properties = {
|
|
683
|
-
component: "query",
|
|
684
|
-
linkUuids: setLinks.map((link) => link.uuid),
|
|
685
|
-
items,
|
|
686
|
-
options: componentOptions,
|
|
687
|
-
collectionProperties
|
|
688
|
-
};
|
|
689
|
-
break;
|
|
690
|
-
}
|
|
691
|
-
case "table": {
|
|
692
|
-
const tableLink = findWebsiteLink(websiteLinks, "set");
|
|
693
|
-
if (tableLink == null) throw new Error(formatComponentError("Table link not found", componentName, elementResource), { cause: componentProperty });
|
|
694
|
-
properties = {
|
|
695
|
-
component: "table",
|
|
696
|
-
linkUuid: tableLink.uuid
|
|
697
|
-
};
|
|
698
|
-
break;
|
|
699
|
-
}
|
|
700
|
-
case "search-bar": {
|
|
701
|
-
const queryVariant = componentReader.valueOr("query-variant", "submit");
|
|
702
|
-
const boundElementUuid = componentReader.uuid("bound-element");
|
|
703
|
-
const href = parseWebsiteLinkTarget(componentReader.valueNode("link-to"), context);
|
|
704
|
-
if (boundElementUuid === null && href === null) throw new Error(formatComponentError("Bound element or href not found", componentName, elementResource), { cause: componentProperty });
|
|
705
|
-
properties = {
|
|
706
|
-
component: "search-bar",
|
|
707
|
-
queryVariant,
|
|
708
|
-
placeholder: componentReader.multilingualValue("placeholder-text", options),
|
|
709
|
-
baseFilterQueries: componentReader.value("base-filter-queries")?.replaceAll(String.raw`\{`, "{").replaceAll(String.raw`\}`, "}") ?? null,
|
|
710
|
-
boundElementUuid,
|
|
711
|
-
href
|
|
712
|
-
};
|
|
713
|
-
break;
|
|
714
|
-
}
|
|
715
|
-
case "text": {
|
|
716
|
-
const content = elementResource.document && "content" in elementResource.document ? parseXMLContent(elementResource.document, options) : null;
|
|
717
|
-
if (content == null) throw new Error(formatComponentError("Content not found", componentName, elementResource), { cause: componentProperty });
|
|
718
|
-
let variantName = "block";
|
|
719
|
-
let variant;
|
|
720
|
-
const variantProperty = componentReader.property("variant");
|
|
721
|
-
if (variantProperty !== null) {
|
|
722
|
-
const variantReader = websitePresentationReader(variantProperty.properties);
|
|
723
|
-
variantName = variantProperty.values[0].content;
|
|
724
|
-
switch (variantName) {
|
|
725
|
-
case "paragraph":
|
|
726
|
-
variant = {
|
|
727
|
-
name: variantName,
|
|
728
|
-
size: variantReader.valueOr("size", "md")
|
|
729
|
-
};
|
|
730
|
-
break;
|
|
731
|
-
case "label":
|
|
732
|
-
variant = {
|
|
733
|
-
name: variantName,
|
|
734
|
-
size: variantReader.valueOr("size", "md")
|
|
735
|
-
};
|
|
736
|
-
break;
|
|
737
|
-
case "heading":
|
|
738
|
-
variant = {
|
|
739
|
-
name: variantName,
|
|
740
|
-
size: variantReader.valueOr("size", "md")
|
|
741
|
-
};
|
|
742
|
-
break;
|
|
743
|
-
case "display":
|
|
744
|
-
variant = {
|
|
745
|
-
name: variantName,
|
|
746
|
-
size: variantReader.valueOr("size", "md")
|
|
747
|
-
};
|
|
748
|
-
break;
|
|
749
|
-
default: variant = { name: variantName };
|
|
750
|
-
}
|
|
751
|
-
} else variant = { name: variantName };
|
|
752
|
-
const headingLevel = componentReader.value("heading-level");
|
|
753
|
-
properties = {
|
|
754
|
-
component: "text",
|
|
755
|
-
variant,
|
|
756
|
-
headingLevel,
|
|
757
|
-
content
|
|
758
|
-
};
|
|
759
|
-
break;
|
|
760
|
-
}
|
|
761
|
-
case "timeline": {
|
|
762
|
-
const timelineLink = findWebsiteLink(websiteLinks, "tree");
|
|
763
|
-
if (timelineLink == null) throw new Error(formatComponentError("Timeline link not found", componentName, elementResource), { cause: componentProperty });
|
|
764
|
-
properties = {
|
|
765
|
-
component: "timeline",
|
|
766
|
-
linkUuid: timelineLink.uuid
|
|
767
|
-
};
|
|
768
|
-
break;
|
|
769
|
-
}
|
|
770
|
-
case "video": {
|
|
771
|
-
const videoLink = findWebsiteLink(websiteLinks, "resource", (link) => link.type === "video");
|
|
772
|
-
if (videoLink == null) throw new Error(formatComponentError("Video link not found", componentName, elementResource), { cause: componentProperty });
|
|
773
|
-
const isChaptersDisplayed = componentReader.valueOr("chapters-displayed", true);
|
|
774
|
-
properties = {
|
|
775
|
-
component: "video",
|
|
776
|
-
linkUuid: videoLink.uuid,
|
|
777
|
-
isChaptersDisplayed
|
|
778
|
-
};
|
|
779
|
-
break;
|
|
780
|
-
}
|
|
789
|
+
case "3d-viewer": return parse3dViewerComponent(parameters);
|
|
790
|
+
case "advanced-search": return parseAdvancedSearchComponent(parameters);
|
|
791
|
+
case "annotated-document": return parseAnnotatedDocumentComponent(parameters);
|
|
792
|
+
case "annotated-image": return parseAnnotatedImageComponent(parameters);
|
|
793
|
+
case "audio-player": return parseAudioPlayerComponent(parameters);
|
|
794
|
+
case "bibliography": return parseBibliographyComponent(parameters);
|
|
795
|
+
case "button": return parseButtonComponent(parameters);
|
|
796
|
+
case "collection": return parseCollectionComponent(parameters);
|
|
797
|
+
case "empty-space": return parseEmptySpaceComponent(parameters);
|
|
798
|
+
case "entries": return parseEntriesComponent(parameters);
|
|
799
|
+
case "iframe": return parseIframeComponent(parameters);
|
|
800
|
+
case "iiif-viewer": return parseIiifViewerComponent(parameters);
|
|
801
|
+
case "image": return parseImageComponent(parameters);
|
|
802
|
+
case "image-gallery": return parseImageGalleryComponent(parameters);
|
|
803
|
+
case "map": return parseMapComponent(parameters);
|
|
804
|
+
case "query": return parseQueryComponent(parameters);
|
|
805
|
+
case "table": return parseTableComponent(parameters);
|
|
806
|
+
case "search-bar": return parseSearchBarComponent(parameters);
|
|
807
|
+
case "text": return parseTextComponent(parameters);
|
|
808
|
+
case "timeline": return parseTimelineComponent(parameters);
|
|
809
|
+
case "video": return parseVideoComponent(parameters);
|
|
781
810
|
default: throw new Error(`Invalid or non-implemented component name “${unparsedComponentName.toString()}” for the following element: “${parseStringContent(elementResource.identification.label, options)}”`);
|
|
782
811
|
}
|
|
783
|
-
return properties;
|
|
784
812
|
}
|
|
785
813
|
function parseWebTitle(properties, identification, overrides) {
|
|
786
814
|
const title = {
|
|
@@ -813,7 +841,7 @@ function parseWebTitle(properties, identification, overrides) {
|
|
|
813
841
|
*/
|
|
814
842
|
function parseWebElement(elementResource, options, context) {
|
|
815
843
|
const identification = parseIdentification(elementResource.identification, options);
|
|
816
|
-
const elementProperties =
|
|
844
|
+
const elementProperties = parseSimplifiedProperties(elementResource.properties, options);
|
|
817
845
|
const presentationProperty = websitePresentationReader(elementProperties).requiredProperty("presentation", `Presentation property not found for element (${formatXMLWebsiteResourceMetadata(elementResource)})`);
|
|
818
846
|
const properties = parseWebElementProperties(websitePresentationReader(presentationProperty.properties).requiredProperty("component", `Component property not found for element (${formatXMLWebsiteResourceMetadata(elementResource)})`), elementResource, options, context);
|
|
819
847
|
const cssStyles = parseResponsiveCssStyles(elementProperties);
|
|
@@ -834,6 +862,51 @@ function parseWebElement(elementResource, options, context) {
|
|
|
834
862
|
...properties
|
|
835
863
|
};
|
|
836
864
|
}
|
|
865
|
+
function isSidebarResource(resource, options) {
|
|
866
|
+
const resourceProperties = parseSimplifiedProperties(resource.properties, options);
|
|
867
|
+
const resourceReader = websitePresentationReader(resourceProperties);
|
|
868
|
+
return resourceReader.value("presentation") === "element" && resourceReader.nestedByValue("presentation", "element").value("component") === "sidebar";
|
|
869
|
+
}
|
|
870
|
+
function parseWebBlockItems(resources, options, context) {
|
|
871
|
+
const items = [];
|
|
872
|
+
for (const resource of resources) {
|
|
873
|
+
const resourceProperties = parseSimplifiedProperties(resource.properties, options);
|
|
874
|
+
const resourceType = websitePresentationReader(resourceProperties).value("presentation");
|
|
875
|
+
if (resourceType === null) continue;
|
|
876
|
+
switch (resourceType) {
|
|
877
|
+
case "element":
|
|
878
|
+
items.push(parseWebElement(resource, options, context));
|
|
879
|
+
break;
|
|
880
|
+
case "block": {
|
|
881
|
+
const block = parseWebBlock(resource, options, context);
|
|
882
|
+
if (block) items.push(block);
|
|
883
|
+
break;
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
return items;
|
|
888
|
+
}
|
|
889
|
+
function parseWebpageRedirect(redirectValue, context) {
|
|
890
|
+
const redirectTarget = parseWebsiteLinkTarget(redirectValue, context);
|
|
891
|
+
if (redirectTarget != null) {
|
|
892
|
+
if (redirectValue?.href == null && redirectValue?.uuid != null) return {
|
|
893
|
+
type: "page",
|
|
894
|
+
slug: redirectTarget,
|
|
895
|
+
uuid: redirectValue.uuid
|
|
896
|
+
};
|
|
897
|
+
return {
|
|
898
|
+
type: "url",
|
|
899
|
+
href: redirectTarget,
|
|
900
|
+
isExternal: redirectTarget.startsWith("http")
|
|
901
|
+
};
|
|
902
|
+
}
|
|
903
|
+
if (redirectValue?.uuid != null) return {
|
|
904
|
+
type: "item",
|
|
905
|
+
uuid: redirectValue.uuid,
|
|
906
|
+
pageType: "item"
|
|
907
|
+
};
|
|
908
|
+
return null;
|
|
909
|
+
}
|
|
837
910
|
/**
|
|
838
911
|
* Parses raw webpage data into a standardized Webpage structure
|
|
839
912
|
*
|
|
@@ -841,7 +914,7 @@ function parseWebElement(elementResource, options, context) {
|
|
|
841
914
|
* @returns Parsed Webpage object
|
|
842
915
|
*/
|
|
843
916
|
function parseWebpage(webpageResource, options, context, slugPrefix) {
|
|
844
|
-
const webpageProperties =
|
|
917
|
+
const webpageProperties = parseSimplifiedProperties(webpageResource.properties, options);
|
|
845
918
|
const webpageReader = websitePresentationReader(webpageProperties);
|
|
846
919
|
if (webpageReader.value("presentation") !== "page") return null;
|
|
847
920
|
const identification = parseIdentification(webpageResource.identification, options);
|
|
@@ -874,27 +947,8 @@ function parseWebpage(webpageResource, options, context, slugPrefix) {
|
|
|
874
947
|
webpages: []
|
|
875
948
|
};
|
|
876
949
|
const imageLink = findWebsiteLink(parseLinks(webpageResource.links, options), "resource", (link) => link.type === "image" || link.type === "IIIF");
|
|
877
|
-
const webpageResources =
|
|
878
|
-
|
|
879
|
-
for (const resource of webpageResources) {
|
|
880
|
-
const resourceProperties = resource.properties != null ? parseSimplifiedProperties(resource.properties, options) : [];
|
|
881
|
-
const resourceType = websitePresentationReader(resourceProperties).value("presentation");
|
|
882
|
-
if (resourceType === null) continue;
|
|
883
|
-
switch (resourceType) {
|
|
884
|
-
case "element": {
|
|
885
|
-
if (websitePresentationReader(resourceProperties).nestedByValue("presentation", "element").value("component") === "sidebar") continue;
|
|
886
|
-
const element = parseWebElement(resource, options, context);
|
|
887
|
-
items.push(element);
|
|
888
|
-
break;
|
|
889
|
-
}
|
|
890
|
-
case "block": {
|
|
891
|
-
const block = parseWebBlock(resource, options, context);
|
|
892
|
-
if (block) items.push(block);
|
|
893
|
-
break;
|
|
894
|
-
}
|
|
895
|
-
}
|
|
896
|
-
}
|
|
897
|
-
returnWebpage.items = items;
|
|
950
|
+
const webpageResources = normalizeWebsiteResources(webpageResource.resource);
|
|
951
|
+
returnWebpage.items = parseWebBlockItems(webpageResources.filter((resource) => !isSidebarResource(resource, options)), options, context);
|
|
898
952
|
returnWebpage.webpages = parseWebpages(webpageResources, options, context, slugPrefix == null ? void 0 : returnWebpage.slug);
|
|
899
953
|
returnWebpage.segments = parseWebsiteSegments(webpageResource.resource, context, options, returnWebpage.slug);
|
|
900
954
|
returnWebpage.properties.sidebar = parseSidebar(webpageResources, options, context);
|
|
@@ -906,24 +960,7 @@ function parseWebpage(webpageResource, options, context, slugPrefix) {
|
|
|
906
960
|
returnWebpage.properties.isSidebarDisplayed = pageReader.valueOr("sidebar-displayed", true);
|
|
907
961
|
returnWebpage.properties.isBreadcrumbsDisplayed = pageReader.valueOr("breadcrumbs-displayed", false);
|
|
908
962
|
returnWebpage.properties.isNavbarSearchBarDisplayed = pageReader.valueOr("navbar-search-bar-displayed", true);
|
|
909
|
-
|
|
910
|
-
const redirectTarget = parseWebsiteLinkTarget(redirectValue, context);
|
|
911
|
-
if (redirectTarget != null) {
|
|
912
|
-
if (redirectValue?.href == null && redirectValue?.uuid != null) returnWebpage.properties.redirect = {
|
|
913
|
-
type: "page",
|
|
914
|
-
slug: redirectTarget,
|
|
915
|
-
uuid: redirectValue.uuid
|
|
916
|
-
};
|
|
917
|
-
else returnWebpage.properties.redirect = {
|
|
918
|
-
type: "url",
|
|
919
|
-
href: redirectTarget,
|
|
920
|
-
isExternal: redirectTarget.startsWith("http")
|
|
921
|
-
};
|
|
922
|
-
} else if (redirectValue?.uuid != null) returnWebpage.properties.redirect = {
|
|
923
|
-
type: "item",
|
|
924
|
-
uuid: redirectValue.uuid,
|
|
925
|
-
pageType: "item"
|
|
926
|
-
};
|
|
963
|
+
returnWebpage.properties.redirect = parseWebpageRedirect(pageReader.valueNode("redirect-to"), context);
|
|
927
964
|
}
|
|
928
965
|
if (imageLink != null) returnWebpage.properties.backgroundImage = {
|
|
929
966
|
uuid: imageLink.uuid,
|
|
@@ -973,86 +1010,72 @@ function parseWebsiteSegments(resources, context, options, slugPrefix) {
|
|
|
973
1010
|
* @returns Parsed Sidebar object
|
|
974
1011
|
*/
|
|
975
1012
|
function parseSidebar(resources, options, context) {
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
const
|
|
982
|
-
|
|
983
|
-
tablet: [],
|
|
984
|
-
mobile: []
|
|
985
|
-
};
|
|
986
|
-
const sidebarResource = resources.find((resource) => {
|
|
987
|
-
const resourceProperties = resource.properties ? parseSimplifiedProperties(resource.properties, options) : [];
|
|
988
|
-
const resourceReader = websitePresentationReader(resourceProperties);
|
|
989
|
-
return resourceReader.value("presentation") === "element" && resourceReader.nestedByValue("presentation", "element").value("component") === "sidebar";
|
|
990
|
-
});
|
|
991
|
-
if (sidebarResource != null) {
|
|
992
|
-
const sidebarBaseProperties = sidebarResource.properties ? parseSimplifiedProperties(sidebarResource.properties, options) : [];
|
|
993
|
-
title = parseWebTitle(sidebarBaseProperties, parseIdentification(sidebarResource.identification, options));
|
|
994
|
-
const sidebarReader = websitePresentationReader(sidebarBaseProperties).nestedByValue("presentation", "element").nestedByValue("component", "sidebar");
|
|
995
|
-
layout = sidebarReader.valueOr("layout", "start");
|
|
996
|
-
mobileLayout = sidebarReader.valueOr("layout-mobile", "default");
|
|
997
|
-
const parsedCssStyles = parseResponsiveCssStyles(sidebarBaseProperties);
|
|
998
|
-
cssStyles.default = parsedCssStyles.default;
|
|
999
|
-
cssStyles.tablet = parsedCssStyles.tablet;
|
|
1000
|
-
cssStyles.mobile = parsedCssStyles.mobile;
|
|
1001
|
-
const sidebarResources = sidebarResource.resource ? normalizeWebsiteResources(sidebarResource.resource) : [];
|
|
1002
|
-
for (const resource of sidebarResources) {
|
|
1003
|
-
const resourceProperties = resource.properties ? parseSimplifiedProperties(resource.properties, options) : [];
|
|
1004
|
-
const resourceType = websitePresentationReader(resourceProperties).value("presentation");
|
|
1005
|
-
if (resourceType === null) continue;
|
|
1006
|
-
switch (resourceType) {
|
|
1007
|
-
case "element": {
|
|
1008
|
-
const element = parseWebElement(resource, options, context);
|
|
1009
|
-
items.push(element);
|
|
1010
|
-
break;
|
|
1011
|
-
}
|
|
1012
|
-
case "block": {
|
|
1013
|
-
const block = parseWebBlock(resource, options, context);
|
|
1014
|
-
if (block) items.push(block);
|
|
1015
|
-
break;
|
|
1016
|
-
}
|
|
1017
|
-
}
|
|
1018
|
-
}
|
|
1019
|
-
}
|
|
1020
|
-
if (title != null && items.length > 0) returnSidebar = {
|
|
1013
|
+
const sidebarResource = resources.find((resource) => isSidebarResource(resource, options));
|
|
1014
|
+
if (sidebarResource == null) return null;
|
|
1015
|
+
const sidebarBaseProperties = parseSimplifiedProperties(sidebarResource.properties, options);
|
|
1016
|
+
const items = parseWebBlockItems(normalizeWebsiteResources(sidebarResource.resource), options, context);
|
|
1017
|
+
if (items.length === 0) return null;
|
|
1018
|
+
const sidebarReader = websitePresentationReader(sidebarBaseProperties).nestedByValue("presentation", "element").nestedByValue("component", "sidebar");
|
|
1019
|
+
return {
|
|
1021
1020
|
isDisplayed: true,
|
|
1022
1021
|
items,
|
|
1023
|
-
title,
|
|
1024
|
-
layout,
|
|
1025
|
-
mobileLayout,
|
|
1026
|
-
cssStyles
|
|
1022
|
+
title: parseWebTitle(sidebarBaseProperties, parseIdentification(sidebarResource.identification, options)),
|
|
1023
|
+
layout: sidebarReader.valueOr("layout", "start"),
|
|
1024
|
+
mobileLayout: sidebarReader.valueOr("layout-mobile", "default"),
|
|
1025
|
+
cssStyles: parseResponsiveCssStyles(sidebarBaseProperties)
|
|
1027
1026
|
};
|
|
1028
|
-
return returnSidebar;
|
|
1029
1027
|
}
|
|
1030
1028
|
function parseWebAccordionItem(elementResource, childResources, options, context) {
|
|
1031
1029
|
const trigger = parseWebElement(elementResource, options, context);
|
|
1032
|
-
const items =
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1030
|
+
const items = parseWebBlockItems(childResources, options, context);
|
|
1031
|
+
return {
|
|
1032
|
+
uuid: trigger.uuid,
|
|
1033
|
+
type: "accordion-item",
|
|
1034
|
+
trigger,
|
|
1035
|
+
items
|
|
1036
|
+
};
|
|
1037
|
+
}
|
|
1038
|
+
function parseBlockOverwrite(overwriteReader, isDefaultLayoutAccordion) {
|
|
1039
|
+
if (overwriteReader.size === 0) return null;
|
|
1040
|
+
const properties = {
|
|
1041
|
+
layout: overwriteReader.value("layout") ?? void 0,
|
|
1042
|
+
wrap: overwriteReader.value("wrap") ?? void 0,
|
|
1043
|
+
spacing: overwriteReader.value("spacing") ?? void 0,
|
|
1044
|
+
gap: overwriteReader.value("gap") ?? void 0,
|
|
1045
|
+
isAccordionEnabled: void 0,
|
|
1046
|
+
isAccordionExpandedByDefault: void 0,
|
|
1047
|
+
isAccordionSidebarDisplayed: void 0
|
|
1048
|
+
};
|
|
1049
|
+
if (isDefaultLayoutAccordion || properties.layout === "accordion") {
|
|
1050
|
+
properties.isAccordionEnabled = overwriteReader.value("accordion-enabled") ?? void 0;
|
|
1051
|
+
properties.isAccordionExpandedByDefault = overwriteReader.value("accordion-expanded") ?? void 0;
|
|
1052
|
+
properties.isAccordionSidebarDisplayed = overwriteReader.value("accordion-sidebar-displayed") ?? void 0;
|
|
1053
|
+
}
|
|
1054
|
+
const cleanedProperties = cleanObject(properties);
|
|
1055
|
+
return Object.keys(cleanedProperties).length > 0 ? cleanedProperties : null;
|
|
1056
|
+
}
|
|
1057
|
+
function parseAccordionAwareBlockItems(blockResources, options, context, isSupportingAccordionItems) {
|
|
1058
|
+
const blockItems = [];
|
|
1059
|
+
for (const resource of blockResources) {
|
|
1060
|
+
const resourceProperties = parseSimplifiedProperties(resource.properties, options);
|
|
1061
|
+
const resourceReader = websitePresentationReader(resourceProperties);
|
|
1062
|
+
const resourceType = resourceReader.value("presentation");
|
|
1036
1063
|
if (resourceType === null) continue;
|
|
1037
1064
|
switch (resourceType) {
|
|
1038
1065
|
case "element": {
|
|
1039
|
-
const
|
|
1040
|
-
|
|
1066
|
+
const childResources = normalizeWebsiteResources(resource.resource);
|
|
1067
|
+
const componentType = resourceReader.nestedByValue("presentation", "element").value("component");
|
|
1068
|
+
blockItems.push(isSupportingAccordionItems && componentType === "text" && childResources.length > 0 ? parseWebAccordionItem(resource, childResources, options, context) : parseWebElement(resource, options, context));
|
|
1041
1069
|
break;
|
|
1042
1070
|
}
|
|
1043
1071
|
case "block": {
|
|
1044
1072
|
const block = parseWebBlock(resource, options, context);
|
|
1045
|
-
if (block)
|
|
1073
|
+
if (block) blockItems.push(block);
|
|
1046
1074
|
break;
|
|
1047
1075
|
}
|
|
1048
1076
|
}
|
|
1049
1077
|
}
|
|
1050
|
-
return
|
|
1051
|
-
uuid: trigger.uuid,
|
|
1052
|
-
type: "accordion-item",
|
|
1053
|
-
trigger,
|
|
1054
|
-
items
|
|
1055
|
-
};
|
|
1078
|
+
return blockItems;
|
|
1056
1079
|
}
|
|
1057
1080
|
/**
|
|
1058
1081
|
* Parses raw block data into a standardized WebBlock structure
|
|
@@ -1061,7 +1084,7 @@ function parseWebAccordionItem(elementResource, childResources, options, context
|
|
|
1061
1084
|
* @returns Parsed WebBlock object
|
|
1062
1085
|
*/
|
|
1063
1086
|
function parseWebBlock(blockResource, options, context) {
|
|
1064
|
-
const blockProperties =
|
|
1087
|
+
const blockProperties = parseSimplifiedProperties(blockResource.properties, options);
|
|
1065
1088
|
const returnBlock = {
|
|
1066
1089
|
uuid: blockResource.uuid,
|
|
1067
1090
|
language: blockResource.lang ?? null,
|
|
@@ -1095,74 +1118,11 @@ function parseWebBlock(blockResource, options, context) {
|
|
|
1095
1118
|
}
|
|
1096
1119
|
returnBlock.properties.default.spacing = blockReader.valueOr("spacing", null);
|
|
1097
1120
|
returnBlock.properties.default.gap = blockReader.valueOr("gap", null);
|
|
1098
|
-
const
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
layout: tabletOverwriteReader.value("layout") ?? void 0,
|
|
1102
|
-
wrap: tabletOverwriteReader.value("wrap") ?? void 0,
|
|
1103
|
-
spacing: tabletOverwriteReader.value("spacing") ?? void 0,
|
|
1104
|
-
gap: tabletOverwriteReader.value("gap") ?? void 0,
|
|
1105
|
-
isAccordionEnabled: void 0,
|
|
1106
|
-
isAccordionExpandedByDefault: void 0,
|
|
1107
|
-
isAccordionSidebarDisplayed: void 0
|
|
1108
|
-
};
|
|
1109
|
-
if (propertiesTablet.layout === "accordion" || returnBlock.properties.default.layout === "accordion") {
|
|
1110
|
-
propertiesTablet.isAccordionEnabled = tabletOverwriteReader.value("accordion-enabled") ?? void 0;
|
|
1111
|
-
propertiesTablet.isAccordionExpandedByDefault = tabletOverwriteReader.value("accordion-expanded") ?? void 0;
|
|
1112
|
-
propertiesTablet.isAccordionSidebarDisplayed = tabletOverwriteReader.value("accordion-sidebar-displayed") ?? void 0;
|
|
1113
|
-
}
|
|
1114
|
-
const cleanedPropertiesTablet = cleanObject(propertiesTablet);
|
|
1115
|
-
if (Object.keys(cleanedPropertiesTablet).length > 0) returnBlock.properties.tablet = cleanedPropertiesTablet;
|
|
1116
|
-
}
|
|
1117
|
-
const mobileOverwriteReader = blockReader.nested("overwrite-mobile");
|
|
1118
|
-
if (mobileOverwriteReader.size > 0) {
|
|
1119
|
-
const propertiesMobile = {
|
|
1120
|
-
layout: mobileOverwriteReader.value("layout") ?? void 0,
|
|
1121
|
-
wrap: mobileOverwriteReader.value("wrap") ?? void 0,
|
|
1122
|
-
spacing: mobileOverwriteReader.value("spacing") ?? void 0,
|
|
1123
|
-
gap: mobileOverwriteReader.value("gap") ?? void 0,
|
|
1124
|
-
isAccordionEnabled: void 0,
|
|
1125
|
-
isAccordionExpandedByDefault: void 0,
|
|
1126
|
-
isAccordionSidebarDisplayed: void 0
|
|
1127
|
-
};
|
|
1128
|
-
if (propertiesMobile.layout === "accordion" || returnBlock.properties.default.layout === "accordion") {
|
|
1129
|
-
propertiesMobile.isAccordionEnabled = mobileOverwriteReader.value("accordion-enabled") ?? void 0;
|
|
1130
|
-
propertiesMobile.isAccordionExpandedByDefault = mobileOverwriteReader.value("accordion-expanded") ?? void 0;
|
|
1131
|
-
propertiesMobile.isAccordionSidebarDisplayed = mobileOverwriteReader.value("accordion-sidebar-displayed") ?? void 0;
|
|
1132
|
-
}
|
|
1133
|
-
const cleanedPropertiesMobile = cleanObject(propertiesMobile);
|
|
1134
|
-
if (Object.keys(cleanedPropertiesMobile).length > 0) returnBlock.properties.mobile = cleanedPropertiesMobile;
|
|
1135
|
-
}
|
|
1136
|
-
}
|
|
1137
|
-
const blockResources = blockResource.resource ? normalizeWebsiteResources(blockResource.resource) : [];
|
|
1138
|
-
const isSupportingAccordionItems = returnBlock.properties.default.layout === "accordion" || returnBlock.properties.tablet?.layout === "accordion" || returnBlock.properties.mobile?.layout === "accordion";
|
|
1139
|
-
const blockItems = [];
|
|
1140
|
-
for (const resource of blockResources) {
|
|
1141
|
-
const resourceProperties = resource.properties ? parseSimplifiedProperties(resource.properties, options) : [];
|
|
1142
|
-
const resourceReader = websitePresentationReader(resourceProperties);
|
|
1143
|
-
const resourceType = resourceReader.value("presentation");
|
|
1144
|
-
if (resourceType === null) continue;
|
|
1145
|
-
switch (resourceType) {
|
|
1146
|
-
case "element": {
|
|
1147
|
-
const childResources = resource.resource ? normalizeWebsiteResources(resource.resource) : [];
|
|
1148
|
-
const componentType = resourceReader.nestedByValue("presentation", "element").value("component");
|
|
1149
|
-
if (isSupportingAccordionItems && componentType === "text" && childResources.length > 0) {
|
|
1150
|
-
const item = parseWebAccordionItem(resource, childResources, options, context);
|
|
1151
|
-
blockItems.push(item);
|
|
1152
|
-
break;
|
|
1153
|
-
}
|
|
1154
|
-
const element = parseWebElement(resource, options, context);
|
|
1155
|
-
blockItems.push(element);
|
|
1156
|
-
break;
|
|
1157
|
-
}
|
|
1158
|
-
case "block": {
|
|
1159
|
-
const block = parseWebBlock(resource, options, context);
|
|
1160
|
-
if (block) blockItems.push(block);
|
|
1161
|
-
break;
|
|
1162
|
-
}
|
|
1163
|
-
}
|
|
1121
|
+
const isDefaultLayoutAccordion = returnBlock.properties.default.layout === "accordion";
|
|
1122
|
+
returnBlock.properties.tablet = parseBlockOverwrite(blockReader.nested("overwrite-tablet"), isDefaultLayoutAccordion);
|
|
1123
|
+
returnBlock.properties.mobile = parseBlockOverwrite(blockReader.nested("overwrite-mobile"), isDefaultLayoutAccordion);
|
|
1164
1124
|
}
|
|
1165
|
-
returnBlock.items =
|
|
1125
|
+
returnBlock.items = parseAccordionAwareBlockItems(normalizeWebsiteResources(blockResource.resource), options, context, returnBlock.properties.default.layout === "accordion" || returnBlock.properties.tablet?.layout === "accordion" || returnBlock.properties.mobile?.layout === "accordion");
|
|
1166
1126
|
returnBlock.cssStyles = parseResponsiveCssStyles(blockProperties);
|
|
1167
1127
|
return returnBlock;
|
|
1168
1128
|
}
|
|
@@ -1317,7 +1277,8 @@ function parseContextItem(contextItemToParse, options) {
|
|
|
1317
1277
|
return {
|
|
1318
1278
|
context: levels,
|
|
1319
1279
|
type,
|
|
1320
|
-
identification: parseIdentification(contextItemToParse.identification, options)
|
|
1280
|
+
identification: parseIdentification(contextItemToParse.identification, options),
|
|
1281
|
+
description: parseContentLike(contextItemToParse.description, options)
|
|
1321
1282
|
};
|
|
1322
1283
|
}
|
|
1323
1284
|
function parseFilterContextDisplay(filterOption) {
|
|
@@ -1401,7 +1362,7 @@ function parseWebsiteTree(websiteTree, context, type, options, slugPrefix) {
|
|
|
1401
1362
|
function parseWebsite(data, options) {
|
|
1402
1363
|
const rawOchre = data.result.ochre;
|
|
1403
1364
|
const metadataLanguages = parseMetadataLanguages(rawOchre);
|
|
1404
|
-
const languages = resolveLanguages(options?.languages
|
|
1365
|
+
const languages = resolveLanguages(options?.languages, metadataLanguages);
|
|
1405
1366
|
const parserOptions = { languages };
|
|
1406
1367
|
const defaultLanguage = resolveDefaultLanguage(rawOchre, languages);
|
|
1407
1368
|
const websiteTree = rawOchre.tree[0];
|