\n \n );\n });\n\n return forms.length > 0 ? <>{forms}> : ;\n};\n\nexport const fragment = graphql`\n fragment MailChimpOptInFormBlockQuery on block_content__mailchimp_opt_in_formConnection {\n nodes {\n id\n drupal_id\n info\n field_enabled\n field_include_or_exclude_urls\n field_url_pattern\n internal {\n type\n }\n relationships {\n field_mailchimp_form_paragraph {\n field_action\n field_bot_key\n field_button_text\n field_form_title\n field_form_text {\n value\n processed\n }\n field_padding\n relationships {\n field_photo_1 {\n ...MediaDataContentImageThumbnail\n }\n field_photo_2_ {\n ...MediaDataContentImageThumbnail\n }\n field_photo_3 {\n ...MediaDataContentImageThumbnail\n }\n field_photo_4 {\n ...MediaDataContentImageThumbnail\n }\n }\n }\n }\n }\n }\n`;\n","import React from 'react';\nimport { graphql } from 'gatsby';\nimport { isDummyOrTestContent, showTestContent } from '../../functions/common';\nimport { DevAids } from '../dev-aids';\n\nexport const MessageSomePagesBlock = ({ nodeID, nodes, callingPark }) => {\n const blocks = nodes?.nodes.map((item, index: Number) => {\n const targetPark = item?.field_site_for_block?.drupal_internal__target_id;\n const enabled = item?.field_enabled;\n const allowedPagesObj = item?.field_show_on_pages;\n const messageType = item?.field_message_type;\n const body = item?.body?.processed;\n const info = item?.info;\n\n // Check if the Block is permitted to appear on the calling page\n //? It may be possible to do this with niftier GraphQL filtering\n const allowedOnThisPage = () => {\n for (let i = 0; i < allowedPagesObj.length; i++) {\n if (allowedPagesObj[i].drupal_internal__target_id === nodeID) {\n return true;\n }\n }\n return false;\n };\n\n return body &&\n // Compare the calling page's Park to the Block's target Park to ensure no\n // cross-site contamination. This should be done in the calling query, but\n // let's be sure\n enabled === true &&\n callingPark === targetPark &&\n allowedOnThisPage() &&\n (!isDummyOrTestContent(info) || (isDummyOrTestContent(info) && showTestContent)) ? (\n \n \n \n \n ) : (\n \n );\n });\n return <>{blocks}>;\n};\n\nexport const fragment = graphql`\n fragment MessageSomePagesBlockQuery on block_content__message_some_pagesConnection {\n nodes {\n id\n drupal_id\n internal {\n type\n }\n info\n field_site_for_block {\n drupal_internal__target_id\n }\n body {\n processed\n }\n field_enabled\n field_message_type\n field_show_on_pages {\n drupal_internal__target_id\n }\n }\n }\n`;\n","import React from 'react';\nimport { graphql } from 'gatsby';\nimport { isDummyOrTestContent, showTestContent, randomKey } from '../../functions/common';\nimport { DevAids } from '../dev-aids';\n\nexport const SiteWideMessageBlock = ({ nodes, callingPark }) => {\n const blocks = nodes.nodes.map((item, index: Number) => {\n const targetPark = item?.field_site_for_block?.drupal_internal__target_id;\n const enabled = item?.field_enabled;\n const messageType = item?.field_message_type;\n const body = item?.body?.processed;\n const info = item?.info;\n\n return body &&\n enabled === true &&\n callingPark === targetPark &&\n (!isDummyOrTestContent(info) || (isDummyOrTestContent(info) && showTestContent)) ? (\n \n \n \n \n ) : (\n \n );\n });\n return <>{blocks}>;\n};\n\nexport const fragment = graphql`\n fragment SiteWideMessageBlockQuery on block_content__site_wide_messageConnection {\n nodes {\n id\n drupal_id\n internal {\n type\n }\n field_site_for_block {\n drupal_internal__target_id\n }\n body {\n processed\n }\n info\n field_enabled\n field_message_type\n }\n }\n`;\n","import React from 'react';\nimport { graphql } from 'gatsby';\nimport { TileImagesParagraph } from '../paragraphs/tile-images';\nimport { DevAids } from '../dev-aids';\nimport { isDummyOrTestContent, showTestContent, currentPageMatchesUrlPattern } from '../../functions/common';\n\nexport const TileImagesSomePagesBlocks = ({ nodes, pagePath }) => {\n if (!nodes) return <>>;\n\n const park = process.env.GATSBY_PARK;\n\n // Loop through the blocks and render them in a stack\n const blocks = nodes.nodes.map((block, index: number) => {\n const targetPark = block?.field_site_for_block?.drupal_internal__target_id,\n enabled = block?.field_enabled,\n includeUrls = block?.field_include_or_exclude_urls,\n urlPattern = block?.field_url_pattern || [],\n body = block?.body?.processed,\n info = block?.info,\n tiles = block.relationships?.field_tile_image_groups || null;\n\n let renderOnThisPage = currentPageMatchesUrlPattern(urlPattern, includeUrls, pagePath);\n\n // If the block is not enabled, or the park is missing or doesn't match, or\n // the block is set to include only certain pages and but the URL list is\n // empty, don't render the block\n if (\n !targetPark ||\n targetPark !== park ||\n enabled === false ||\n (includeUrls === true && urlPattern.length < 1)\n ) {\n return ;\n }\n\n return body &&\n tiles &&\n renderOnThisPage &&\n (!isDummyOrTestContent(info) || (isDummyOrTestContent(info) && showTestContent)) ? (\n
\n \n
\n
\n
\n
\n \n
\n
\n
\n
\n \n
\n ) : (\n \n );\n });\n\n // Render the blocks\n return blocks.length > 0 ? (\n <>{blocks}>\n ) : (\n \n );\n};\n\nexport const fragment = graphql`\n fragment TileImagesSomePagesBlockQuery on block_content__tile_images_some_pagesConnection {\n nodes {\n id\n drupal_id\n info\n body {\n processed\n }\n internal {\n type\n }\n field_site_for_block {\n drupal_internal__target_id\n }\n field_enabled\n field_include_or_exclude_urls\n field_url_pattern\n relationships {\n field_tile_image_groups {\n ...ParagraphTileImages\n }\n }\n }\n }\n`;\n","import React from 'react';\nimport Link from '../elements/link';\nimport { GatsbyImage, getImage } from 'gatsby-plugin-image';\n\n// This file refers to the 'buttons' (plural) paragraph type in Drupal,\n// just to add to confusion\n\nexport default function Button({\n title,\n url,\n style,\n backgroundImage = undefined,\n backgroundImageAltText = '',\n ...props\n}) {\n if (!url) return <>>;\n\n // These class names should match the option's machine names given in Drupal:\n // /admin/structure/paragraphs_type/buttons/fields/paragraph.buttons.field_style/storage\n const classNames = {\n default: '',\n alternate: 'button--alternate',\n triangle: 'button--with-triangle',\n 'alternate triangle': 'button--alternate button--with-triangle',\n },\n matchedStyles = style ? classNames[style] : '',\n bgImg = backgroundImage ? getImage(backgroundImage) : null;\n\n return bgImg ? (\n \n \n {title && {title}}\n \n ) : (\n \n {title ? title : url}\n \n );\n}\n","import React from 'react';\nimport { graphql } from 'gatsby';\nimport { GatsbyImage, StaticImage, getImage } from 'gatsby-plugin-image';\nimport Slider from 'react-slick';\nimport Link from '../elements/link';\nimport {\n stripUrlParkPrefix,\n getTileImageFieldName,\n convertStringToId,\n testEnv,\n randomKey,\n} from '../../functions/common';\n\nexport const TileImagesParagraph = ({ node, pageContextData }) => {\n const sectionTitle = node.field_title || '',\n includeTilesMap = node.field_include_tiles_map || false,\n tileGroups = node.relationships?.field_tile_image_groups;\n\n // Save a clone of the tileGroup object, as we need an immutable copy to loop\n // over when assessing group content, and another to build the nav that can\n // have empty groups removed without breaking .map() indexing.\n let cleanedTileGroups = structuredClone(tileGroups);\n\n // Stop if there's no data\n if (!node || !tileGroups || !cleanedTileGroups) {\n return ;\n }\n\n // Build the nav wrapper\n // Test if more than one group is used, add tabs if yes\n const buildNavWrapper = (dots) => {\n return tileGroups.length > 1 ? (\n \n ) : (\n \n );\n };\n\n // Build the custom HTML for the SlickJS dots nav - we'll turn them into buttons\n\n // Create individual tiles\n const buildTile = (\n title: string,\n imgSrc: any,\n imgAlt: string = '',\n path: string,\n largeTile: boolean,\n index: number,\n tileType: string = '',\n status: boolean | true\n ) => {\n if (!title || !path) {\n testEnv().devMode\n ? console.log(\n `[ISSUE - Scripts]: Missing required data in buildTile() from 'Tile Images' paragraph type in paragraph ID ${node.id}, skipping...`\n )\n : null;\n return ;\n }\n\n // Handle cases where status may not be defined\n if (typeof status !== 'boolean') {\n status = true;\n }\n\n // Check if image is valid, or fallback to a generic image\n // TODO: Replace this image with default placeholder image for each type\n\n const fallbackImg = '../../images/icon-file-generic.png',\n largeTileClass = largeTile ? ' tile--double-width' : '',\n //tileTypeClass is informational, for locating Drupal source\n // content more easily\n tileTypeClass = tileType ? ` tile--${tileType}` : '',\n titleClass = status ? 'tile__title' : 'tile__title admin-only';\n\n return title && path ? (\n \n
\n
\n {/* Allow graceful fallback for Gatsby not correctly saving a childImageSharp version */}\n {!imgSrc ? (\n \n ) : typeof imgSrc === 'object' ? (\n \n ) : (\n \n )}\n
\n
\n
\n {status ? '' : Not live:}\n {title}\n
\n \n ) : (\n \n );\n };\n\n const groupData = tileGroups.map((group, index: number) => {\n let groupType = group.internal?.type,\n items = [];\n\n // Cull empty paragraphs, which return empty objects\n if (!groupType) {\n return ;\n }\n\n switch (groupType) {\n case 'paragraph__tile_images_content':\n // Content\n\n // Links to Nodes\n for (let item in group.relationships?.field_links) {\n let tileType = 'content',\n tile =\n group.relationships?.field_links[item]?.relationships?.field_content_links || null;\n\n // Check a title and URL exists, and cull any expired Events\n if (tile !== null && tile.title && tile.path.alias) {\n const pageUrl = stripUrlParkPrefix(tile.path.alias),\n mediaField = getTileImageFieldName(tile.internal.type),\n largeTile = group.relationships?.field_links[item].field_show_large_tile,\n imgSrc =\n largeTile === true\n ? getImage(tile.relationships?.[mediaField]?.customLocalFieldMediaImageWide) ||\n tile.relationships?.[mediaField]?.customLocalFieldMediaImageWide?.publicURL ||\n null\n : getImage(tile.relationships?.[mediaField]?.customLocalFieldMediaImage) ||\n tile.relationships?.[mediaField]?.customLocalFieldMediaImage?.publicURL ||\n null,\n imgAlt = tile.relationships?.[mediaField]?.field_media_image?.alt || '',\n tileStatus = tile.status;\n\n const renderedTile =\n buildTile(\n tile.title,\n imgSrc,\n imgAlt,\n pageUrl,\n largeTile,\n tile.id,\n tileType,\n tileStatus\n ) || null;\n\n renderedTile !== null && items.push(renderedTile);\n }\n }\n\n // Links to External URLs\n for (let item in group.relationships?.field_external_links) {\n let tileType = 'content-external',\n tile = group.relationships?.field_external_links[item] || null;\n\n // Check the external link exists\n if (tile !== null && tile.field_external_link.uri) {\n const text = tile.field_external_link.title || tile.field_external_link.uri,\n externalUrl = tile.field_external_link.uri,\n largeTile = tile.field_show_large_tile,\n imgSrc =\n largeTile === true\n ? getImage(tile.relationships?.field_image?.customLocalFieldMediaImageWide) ||\n tile.relationships?.field_image?.customLocalFieldMediaImageWide?.publicURL ||\n null\n : getImage(tile.relationships?.field_image?.customLocalFieldMediaImage) ||\n tile.relationships?.field_image?.customLocalFieldMediaImage?.publicURL ||\n null,\n imgAlt = tile.relationships?.field_image?.field_media_image?.alt;\n\n const renderedTile =\n buildTile(text, imgSrc, imgAlt, externalUrl, largeTile, tile.id, tileType, true) ||\n null;\n\n renderedTile !== null && items.push(renderedTile);\n }\n }\n\n // Links to Documents\n for (let item in group.relationships?.field_documents) {\n let tileType = 'content-document',\n tile = group.relationships?.field_documents[item] || null;\n\n // Check the document's file exists\n if (\n tile !== null &&\n tile.relationships?.field_file?.relationships?.field_media_file !== null\n ) {\n const text = tile.relationships?.field_file?.name,\n fileUrl = tile.relationships?.field_file?.customLocalFieldMediaFile?.publicURL,\n largeTile = tile.field_show_large_tile,\n imgSrc =\n largeTile === true\n ? getImage(tile.relationships?.field_file?.customLocalFieldMediaImageWide) ||\n tile.relationships?.field_file?.customLocalFieldMediaImageWide?.publicURL ||\n null\n : getImage(tile.relationships?.field_file?.customLocalFieldMediaImage) ||\n tile.relationships?.field_file?.customLocalFieldMediaImage?.publicURL ||\n null,\n imgAlt = tile.relationships?.field_file?.field_media_image?.alt,\n fileStatus = tile.status;\n\n const renderedTile =\n buildTile(\n `${text}`,\n imgSrc,\n imgAlt,\n fileUrl,\n largeTile,\n tile.id,\n tileType,\n fileStatus\n ) || null;\n\n renderedTile !== null && items.push(renderedTile);\n }\n }\n break;\n\n case 'paragraph__tile_images_collection':\n // Collection\n\n for (let item in group.relationships?.field_collection?.relationships) {\n let collectionData = group.relationships?.field_collection?.relationships[item];\n\n // The Collection items could originate from many content types, so to avoid\n // calling them all we just cull anything that is empty.\n if (collectionData && collectionData.length > 0) {\n for (let collectionItem in collectionData) {\n collectionItem =\n typeof collectionData[collectionItem] === 'object'\n ? collectionData[collectionItem]\n : null;\n const tileType = 'collection';\n\n // Check a title and URL exists, and cull any expired Events\n if (collectionItem && collectionItem.title && collectionItem.path?.alias) {\n const text = collectionItem.title,\n pageUrl = stripUrlParkPrefix(collectionItem.path?.alias),\n mediaField = getTileImageFieldName(collectionItem.internal?.type),\n // Force square tiles\n largeTile = false,\n imgSrc =\n getImage(\n collectionItem.relationships?.[mediaField]?.customLocalFieldMediaImage\n ) ||\n collectionItem.relationships?.[mediaField]?.customLocalFieldMediaImage\n ?.publicURL ||\n null,\n imgAlt =\n collectionItem.relationships?.[mediaField]?.field_media_image?.alt || null,\n tileStatus = collectionItem.status;\n\n const renderedTile =\n buildTile(\n text,\n imgSrc,\n imgAlt,\n pageUrl,\n largeTile,\n collectionItem.id,\n tileType,\n tileStatus\n ) || null;\n\n renderedTile !== null && items.push(renderedTile);\n }\n }\n }\n }\n break;\n\n case 'paragraph__tile_images_group_view':\n // Views\n //! NOTE: Filtering results should be done in the Drupal View, not here\n // If no viewUrl exists, the View was empty and never created in\n // GraphQL, so skip it\n if (group.relatedCustomDrupalView?.viewUrl !== null) {\n for (let item in group.relatedCustomDrupalView?.viewChildren) {\n item =\n typeof group.relatedCustomDrupalView?.viewChildren[item] === 'object'\n ? group.relatedCustomDrupalView?.viewChildren[item]\n : null;\n\n if (item && item.hasOwnProperty('id')) {\n const text = item.title || null,\n pageUrl = stripUrlParkPrefix(item?.path?.alias) || null,\n // Force square tiles for View items\n largeTile = false,\n tileType = 'view',\n imgMediaType = item.customMediaTileImage\n ? 'customMediaTileImage'\n : 'customMediaInlineImage',\n imgSrc =\n getImage(\n item?.[imgMediaType]?.relationships?.field_media_image?.relationships\n ?.media__tile_image[0]?.customLocalFieldMediaImage\n ) ||\n item?.[imgMediaType]?.relationships?.field_media_image?.relationships\n ?.media__tile_image[0]?.customLocalFieldMediaImage?.publicURL ||\n null,\n imgAlt = item?.[imgMediaType]?.field_media_image?.alt || null,\n tileStatus = item.status;\n\n const renderedTile =\n buildTile(\n text,\n imgSrc,\n imgAlt,\n pageUrl,\n largeTile,\n item.id,\n tileType,\n tileStatus\n ) || null;\n\n renderedTile !== null && items.push(renderedTile);\n }\n }\n }\n break;\n\n default:\n testEnv().devMode\n ? console.warn(\n `[ISSUE - Scripts]: Tile Images groupType ${groupType} not identified, skipping...`\n )\n : null;\n return null;\n }\n\n // Cull empty groups from the cleanedTileGroups array, so we have a 1:1 copy\n // for building the nav\n if (items.length === 0) {\n cleanedTileGroups[index] = null;\n }\n\n // Finally, return a Slide for each set of items, and provide a random key\n return items.length > 0 ? (\n
\n
{items}
\n
\n ) : (\n \n );\n });\n\n // Full React Slick settings list:\n // https://react-slick.neostack.com/docs/api\n const mainSliderSettings = {\n accessibility: true,\n adaptiveHeight: true,\n arrows: false,\n autoplay: false,\n dots: true,\n // dotsAboveSlider requires ./patch/react-slick+0.28.1.patch\n // The MR to include this was denied as they are trying to keep feature\n // parity with Slick.js: https://github.com/akiran/react-slick/issues/2300\n dotsAboveSlider: true,\n fade: true,\n className: 'slick-tiles',\n infinite: false,\n slidesToScroll: 1,\n slidesToShow: 1,\n appendDots: (dots) => buildNavWrapper(dots),\n\n // Build the slider nav from the cloned array, as the original gets mutated\n // while inside it's own .map() and messes up the indexes. This leads to\n // misaligned headings for slides that have been culled.\n // The cleanedTileGroups array should contain 'null' values for groups with\n // no tiles to show, and therefore be culled here.\n customPaging: (i) =>\n cleanedTileGroups[i] && cleanedTileGroups[i].field_title ? (\n
\n ) : (\n \n );\n}\n","import React from 'react';\nimport { graphql } from 'gatsby';\nimport Link from '../elements/link';\nimport {\n getParkNames,\n stripUrlParkPrefix,\n isDummyOrTestContent,\n testEnv,\n} from '../../functions/common';\n\nexport const SocialMediaLinksBlock = ({ node, title, pageURI, summaryText }) => {\n // Generate a fully-qualified URL, as if we share an absolute /path/like/this, the\n // remote service won't know which site we're talking about.\n const fullURI = `https://${getParkNames().domain}${stripUrlParkPrefix(pageURI)}` || null;\n\n const links = node?.relationships?.field_social_media_link || null;\n\n if (links && fullURI && isDummyOrTestContent(node.info) === false) {\n const linksList = links.map((link, index: Number) => {\n const imgSrc =\n link?.relationships?.field_social_media_svg_icon?.customLocalFieldMediaImage1?.publicURL || null,\n serviceName = link.field_social_media_link_title,\n serviceLinkPrefix = link?.field_social_media_link_url?.uri || '';\n\n // Define the text we will append to each social media service,\n // as it varies between services.\n\n let linkStr = '';\n\n switch (serviceName.toLowerCase()) {\n case 'facebook':\n linkStr = fullURI;\n break;\n case 'twitter':\n case 'x':\n // Create the hashtag from the park shortname, and remove any dashes\n linkStr = `${encodeURI(\n title\n )}&url=${fullURI}&hashtag=see${getParkNames().shortName.replace('-', '')}`;\n break;\n case 'reddit':\n linkStr = `${fullURI}&title=${encodeURI(title)}`;\n break;\n case 'pinterest':\n linkStr = `${fullURI}&media=${imgSrc}&description=${encodeURI(title)}`;\n break;\n case 'email':\n summaryText ? (summaryText = `${summaryText}\\n\\n`) : (summaryText = '');\n linkStr = `subject=${encodeURI(title)}${encodeURI(' - ')}${encodeURI(getParkNames().fullName)}&body=${encodeURI(summaryText)}${fullURI}`;\n break;\n default:\n testEnv().devMode\n ? console.warn(\n `[ISSUE - Links]: Unrecognised social media service name in Social Media Block ID ${node.drupal_id}, skipping...`\n )\n : null;\n break;\n }\n\n return (\n
\n \n {imgSrc ? (\n \n ) : (\n \n )}\n \n
\n );\n });\n return linksList ? linksList : ;\n } else {\n return ;\n }\n};\n\nexport const fragment = graphql`\n fragment SocialMediaLinksBlockQuery on block_content__social_media {\n id\n drupal_id\n info\n relationships {\n field_social_media_link {\n id\n field_shared_text\n field_social_media_link_title\n field_social_media_link_url {\n title\n uri\n }\n relationships {\n field_social_media_svg_icon {\n ...MediaDataSvgIcon\n }\n }\n }\n }\n }\n`;\n","import React from 'react';\nimport Link from '../elements/link';\nimport { stripUrlParkPrefix } from '../../functions/common';\n\nexport default function Breadcrumbs(props) {\n if (!props.crumbs) {\n console.warn(`[ISSUE - Menus]: No props.crumbs provided to Breadcrumbs component, skipping...`);\n return <>>;\n }\n let crumbLinks = props.crumbs.map((item) => {\n return (\n
\n {item.node.title}\n
\n );\n });\n\n // Reverse the array, so it renders in the correct order\n crumbLinks.reverse();\n\n // Add homepage to top level may need to map name\n return ;\n}\n","import React from 'react';\nimport { SocialMediaLinksBlock } from '../blocks/social-media-links';\nimport Breadcrumbs from '../structure/breadcrumbs';\nimport { randomKey } from '../../functions/common';\n\nexport default function PageTitleSection({ title, socialMediaBlock, breadcrumbs, summaryText }) {\n return title ? (\n