\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 { convertStringToId} from '../../functions/common.js';\n\nexport default function Iframe({ src, width, height, fullscreen }) {\n if (!src) {\n return null;\n }\n // Set default image sizes to match embedded Youtube video dimensions, unless fullscreen\n if (!width) { width = 800 }\n if (!height) { height = 564 }\n\n const sourceDomain = new URL(src).hostname,\n name = convertStringToId(src);\n\n // If fullscreen, use style attr to force the size to fit the browser window\n // Note some content will resize within this shape to retain aspect ratios e.g. youtube videos\n return fullscreen === true ? (\n \n ) : (\n \n );\n};\n","// @ts-nocheck\nimport React from 'react';\nimport Iframe from './Iframe';\nimport { getKARdata } from '../fragments/kardata';\nimport { getCIARdata } from '../fragments/ciardata';\nimport marineZonesActivities from '../../../static/js/marine-zones-permitted-activities.yml';\nimport { useLocation } from '@reach/router';\nimport { randomKey } from '../../functions/common';\n\nlet whichPark = process.env.GATSBY_PARK;\nlet locationString = null;\n// @ts-nocheck\n\n//! WARNING: For pages located at /access/, this component serves 2 distinctly\n//! separate elements - an embedded map AND a table. Not ideal, but it was\n//! originally designed to just handle the Kakadu Access Report\n\n/**\n * Embeds a map with specified parameters and points.\n * @param {Object} props - The properties for the map component.\n * @param {string} props.title - The title of the embedded map.\n * @param {string} props.wrapperId - The id of the map wrapper.\n * @param {string} props.mapId - Manually set the map id\n * @param {number} props.latNum - The latitude of the map center.\n * @param {number} props.lonNum - The longitude of the map center.\n * @param {number} props.zoom - The zoom level of the map.\n * @param {Array} props.points - The JSON array of points on the map (optional).\n * @param {string} props.style - The visual style of the map, default 'outdoor'\n * @returns {React.Element} - The map embedded in an iframe.\n */\n\nfunction MapEmbed(props) {\n const frameTitle = props.title;\n const latNum = props.latNum;\n const lonNum = props.lonNum;\n const zoom = props.zoom;\n const points = props.points;\n const style = props.style || 'outdoor';\n const location = useLocation();\n const wrapperId = props.wrapperId;\n const mapId = location.pathname === '/access/' ? 'accessReportMap' : props.mapId || 'map';\n const parkHasRegions = whichPark === 'knp' ? true : false;\n\n // Load mapbox for normal map embed\n let sourceDoc = ``;\n\n // Pass in any variables we want the iframe to have, and add common javascript\n // functions, mapbox code, and styles for popup boxes.\n sourceDoc += `\n \n \n \n \n \n `;\n\n // Only load AMP data for AMP site\n if (whichPark === 'amp') {\n sourceDoc += `\n `;\n }\n\n // I have no idea where react is expecting a key to exist that I don't need or use.\n let keyId = 'map_' + Math.floor(Math.random() * 49152);\n let keyId2 = 'map2_' + Math.floor(Math.random() * 49152);\n\n // Show a map and table section for the Kakadu access report\n // Do not use/insert an iframe here, we will do that in code --> jscommon::createAccessReportMap()\n if (location.pathname === '/access/' && (whichPark === 'knp' || whichPark === 'cinp')) {\n const getData = () => {\n if (whichPark === 'knp') {\n return getKARdata();\n } else if (whichPark === 'cinp') {\n return getCIARdata();\n } else {\n return null;\n }\n };\n\n const locationData = getData();\n const locationGraphQLNodeTitle = () => {\n switch (whichPark) {\n case 'knp':\n return 'allNodeKarLocation';\n case 'cinp':\n return 'allNodeCinpLocation';\n default:\n return null;\n }\n };\n const locationTitle = locationGraphQLNodeTitle();\n\n if (!locationTitle) {\n console.log('Map data not found! Omitting map');\n return ;\n }\n\n // Just get the nodes object from the data returned by the query\n locationString = JSON.stringify(locationData[locationTitle].nodes);\n // Remove unwanted HTML tags, newlines and carriage returns from the rendered string\n locationString = locationString\n .replace(/(\\r\\n|\\r|\\n)/gi, '')\n .replace(/
]*>/gi, '')\n .replace(/<\\/p>/gi, '')\n .replace('\\r', '')\n .replace(/<\\/a>/gi, '')\n .replace(/<\\/a>/gi, '')\n .replace('\\n', '');\n\n let mapPoints = [],\n showMapKey = false;\n\n for (let n in locationData[locationTitle].nodes) {\n let node = locationData[locationTitle].nodes[n];\n let newNode = {};\n // Go through the objects/paragraphs and find a lat/lng for this feature\n let lat = parseFloat(node.field_kar_latitude) || null,\n lng = parseFloat(node.field_kar_longitude) || null;\n\n // Assign it to a standard variable name\n node.lat = isNaN(lat) ? null : lat;\n node.lng = isNaN(lng) ? null : lng;\n\n if (node.lat !== null && node.lng !== null) {\n showMapKey = true;\n }\n\n node.comments =\n node.relationships?.field_kar_comments && node.relationships?.field_kar_comments.name\n ? node.relationships?.field_kar_comments.name\n : '';\n node.additionalComments =\n node.field_kar_additional_comments && node.field_kar_additional_comments?.value\n ? node.field_kar_additional_comments?.value\n : '';\n node.additionalComments = node.additionalComments.replace(/(<([^>]+)>)/gi, '');\n\n node.statusDisplay =\n node.field_kar_status.charAt(0).toUpperCase() +\n node.field_kar_status.substr(1).toLowerCase();\n\n // Create a simplified object with only the properties that we need for the map point/popup\n newNode.id = n;\n newNode.title = node.title;\n newNode.lat = node.lat;\n newNode.lng = node.lng;\n newNode.region = '';\n try {\n newNode.region = node.relationships?.field_kakadu_region?.title;\n } catch (e) {}\n newNode.status = node.field_kar_status; // === 'open';\n newNode.statusDisplay =\n newNode.status === 'open_caution'\n ? 'Open, but beware of risks'\n : newNode.status === 'open'\n ? 'Open'\n : 'Closed';\n let statusColour =\n newNode.status === 'open'\n ? ''\n : newNode.status === 'open_caution'\n ? 'border:1px solid #B45F06; background:#F1C232;'\n : 'border:1px solid #e61f00; background:#fcd8d8;';\n newNode.colour =\n newNode.status === 'open'\n ? '#78be20'\n : newNode.status === 'open_caution'\n ? '#FF6600'\n : '#CB1517';\n newNode.bgColour =\n newNode.status === 'open' ? '' : newNode.status === 'open_caution' ? '#f1c232' : '#fcd8d8';\n newNode.comments = node.comments;\n newNode.additionalComments = node.additionalComments;\n newNode.text =\n node.comments +\n `
` +\n newNode.statusDisplay +\n `
`;\n mapPoints.push(newNode);\n }\n // Append a script tag with the locations as an object, then call the\n // function createAccessReportMap() to make the map happen.\n sourceDoc += `\n `;\n\n const buildTableRow = (node) => {\n return (\n
\n \n );\n };\n\n // Sort the map points by region so those without one are first\n if (parkHasRegions) {\n mapPoints.sort((a, b) => {\n return a.region === b.region ? 0 : a.region > b.region ? 1 : -1;\n });\n }\n\n const tableItems = mapPoints.map(function (node, i, array) {\n // Non-KAR locations\n if (!parkHasRegions) {\n return buildTableRow(node);\n } else {\n // KAR locations\n if (i === 0 || (i > 0 && node.region !== array[i - 1].region)) {\n return buildTableSubheaderRow(node);\n } else {\n return buildTableRow(node);\n }\n }\n });\n\n return (\n
\n
\n \n
\n
\n
\n {showMapKey && (\n
\n \n {' '}\n Map legend:\n \n
\n
\n {' '}\n Open\n
\n
\n {' '}\n Open, but beware of risks\n
\n
\n {' '}\n Closed\n
\n
\n
\n )}\n
\n
\n \n
\n
\n Visitor site\n
\n
\n Status\n
\n
\n Comments\n
\n
\n \n {tableItems}\n
\n
\n
\n
\n \n
\n );\n } else {\n // Otherwise, initialise the normal embedded map and shove it in an iframe\n // This object is inside the global 'window' object because I had real hassles getting the\n // Parks information between browsers and when not logged in.\n sourceDoc += ``;\n return <>{}>;\n }\n}\n\nexport default MapEmbed;\n","import React from 'react';\n\nexport default function Script({ src, props }) {\n // Prepare the script markup to prevent it flickering and failing to run.\n // Note if the script inserts content into the page at that point,\n // a warning may be thrown as the dangerouslySetInnerHTML output won't\n // match the below.\n const htmlMarkup = () => {\n return {\n __html: ``,\n };\n };\n\n return ;\n}\n","import React from \"react\";\n\nexport default function YoutubeEmbed(prop){\n return(\n
\n \n
\n )\n}\n","import React, { useRef, useEffect } from 'react';\n\nimport { Fancybox as NativeFancybox } from '@fancyapps/ui';\nimport '@fancyapps/ui/dist/fancybox/fancybox.css';\n\nfunction Fancybox(props) {\n const containerRef = useRef(null);\n // Set any default options here via props.options\n // https://fancyapps.com/fancybox/api/options/\n\n // * NOTE:\n // Setting the slideshow wrapper class via props.options.mainClass\n // causes CSS breaking overlap, so we set it manually below\n\n useEffect(() => {\n const container = containerRef.current,\n delegate = props.delegate || '[data-fancybox]',\n options = props.options || {};\n\n NativeFancybox.bind(container, delegate, options);\n\n return () => {\n NativeFancybox.unbind(container);\n NativeFancybox.close();\n };\n });\n\n return (\n
\n {props.children}\n
\n );\n}\n\nexport default Fancybox;\n","import React from 'react';\nimport { AccordionContainerParagraph } from './accordion-container';\nimport { ButtonsContainerParagraph } from './buttons-container';\nimport { DevAids } from '../dev-aids';\nimport { DownloadsListParagraph } from './downloads-list';\nimport { EmbedIframeParagraph } from './embed-iframe';\nimport { EmbedMapParagraph } from './embed-map';\nimport { EmbedScriptParagraph } from './embed-script';\nimport { EmbedVideoParagraph } from './embed-video';\nimport { FeedListParagraph } from './feed-list';\nimport { FigureParagraph } from './figure';\nimport { GalleryGridParagraph } from './gallery-grid';\nimport { HeroImageSectionParagraph } from './hero-image-section';\nimport { PeopleParagraph } from './people';\nimport { QuoteWithImageParagraph } from './quote-with-image';\nimport { SectionHeadingLinksParagraph } from './section-heading-links';\nimport { TableParagraph } from './table';\nimport { TextParagraph } from './text';\nimport { TileImagesParagraph } from './tile-images';\n\nconst components = {\n paragraph__accordion_container: AccordionContainerParagraph,\n paragraph__button_container: ButtonsContainerParagraph,\n paragraph__downloads_list: DownloadsListParagraph,\n paragraph__embed_iframe: EmbedIframeParagraph,\n paragraph__embed_map: EmbedMapParagraph,\n paragraph__embed_script: EmbedScriptParagraph,\n paragraph__embed_video: EmbedVideoParagraph,\n paragraph__feed_list: FeedListParagraph,\n paragraph__figure: FigureParagraph,\n paragraph__hero_image_section: HeroImageSectionParagraph,\n paragraph__image_gallery_grid: GalleryGridParagraph,\n paragraph__people: PeopleParagraph,\n paragraph__quote_with_image: QuoteWithImageParagraph,\n paragraph__section_heading_links: SectionHeadingLinksParagraph,\n paragraph__table: TableParagraph,\n paragraph__text: TextParagraph,\n paragraph__tile_images: TileImagesParagraph,\n};\n\n//* Pass variables like Gatsby's pageContext data to paragraphs via the callback\n//* function of Array.map():\n//* paragraphs = someParagraphsArray.map((item) => getParagraph(item, pageContext)),\n\nexport const getParagraph = (node, pageContext) => {\n\n const ParagraphComponent = components[node.type];\n return ParagraphComponent !== undefined ? (\n // Include a key wrapper as these are returned to a .map()\n \n {}\n \n \n ) : (\n \n );\n};\n","import React, { useState } from 'react';\nimport { graphql } from 'gatsby';\nimport { AccordionItem } from './accordion-item';\nimport { convertStringToId } from '../../functions/common';\n\nexport const AccordionContainerParagraph = (query) => {\n const items =\n query.node.relationships?.field_accordion_items.map((item, index) => {\n const titleId = convertStringToId(item.field_accordion_item_title?.value),\n [isOpen, setIsOpen] = useState(false);\n\n return item.field_accordion_item_title?.value && item.field_accordion_item_body?.processed ? (\n \n ) : (\n \n );\n }) || null;\n\n return (\n
\n );\n};\n\nexport const fragment = graphql`\n fragment ParagraphAccordionContainer on paragraph__accordion_container {\n id\n drupal_id\n internal {\n type\n }\n field_accordion_container_intro {\n processed\n }\n relationships {\n field_accordion_items {\n id\n field_accordion_item_title {\n value\n }\n field_accordion_item_body {\n processed\n }\n }\n }\n }\n`;\n","import React from 'react';\nimport { graphql } from 'gatsby';\nimport Button from './button';\nimport {\n convertBytesToHumanReadableFileSize,\n getFileExtension,\n stripUrlParkPrefix,\n stripUrlEntityPrefix,\n} from '../../functions/common.js';\n\nexport const ButtonsContainerParagraph = ({ node }) => {\n const alignment = `t-${node.field_align || 'left'}`;\n\n // If there are several choices to use for the URL, prioritise text URL over Media\n const whichPathToUse = (link, file) => {\n if (link) {\n return link;\n } else if (file) {\n return file;\n } else {\n return false;\n }\n };\n\n // Collect the buttons\n const buttons = node.relationships.field_button.map((button, index: Number) => {\n let path;\n\n let text = button.field_title;\n\n // TODO: Buttons allow unlimited links in a single button, but only access\n // TODO: the first. Why tf did I do that.\n const link = button.field_link[0] || null,\n fileMetadata = button.relationships?.field_file?.relationships?.field_media_file || null,\n fileLink =\n button.relationships?.field_file?.customLocalFieldMediaAudioFile?.publicURL ||\n button.relationships?.field_file?.customLocalFieldMediaFile?.publicURL,\n backgroundImage =\n button.relationships?.field_button_background_image?.customLocalFieldMediaImage7 || null,\n backgroundImageAltText =\n button.relationships?.field_button_background_image?.field_media_image_7?.alt || null;\n\n // If Button has no destination, skip it\n if ((!fileLink && (!link || link.length < 1)) || (fileLink && !fileMetadata)) {\n return ;\n }\n\n const file_size = convertBytesToHumanReadableFileSize(fileMetadata?.filesize) || null,\n file_type = getFileExtension(fileMetadata?.filename) || null;\n\n // If Button has both a Media and a URL, decide which to use\n // TODO: link will be a string if we cull it to handle a single value\n if (fileLink && link !== null && link.length >= 1) {\n path = whichPathToUse(link.uri, fileLink);\n } else if (fileLink) {\n // If the button points to a file, include the extension and file size\n path = fileLink;\n file_type && file_size ? (text += ` (${file_type}, ${file_size})`) : text;\n } else {\n // If a link has a URI and a URI Alias, use the alias.\n // If not an alias, remove the Drupal prefix from the path\n link.uri_alias\n ? (path = stripUrlParkPrefix(link.uri_alias))\n : (path = stripUrlEntityPrefix(link.uri));\n }\n\n return (\n \n );\n });\n\n return (\n
\n
\n
\n
\n
{buttons}
\n
\n
\n
\n
\n );\n};\n\n// If updating the Media types a Button Paragraph Type can use, update the\n// fragment here to capture it, then update the logic above to handle the new possibilities:\n// /admin/structure/paragraphs_type/buttons/fields/paragraph.buttons.field_file\n\n// We collect all the child button data here, not in the button component itself.\n// This saves checking the parent paragraph ID against the child button's parent ID.\n\nexport const fragment = graphql`\n fragment ParagraphButtonsContainer on paragraph__button_container {\n id\n drupal_id\n internal {\n type\n }\n field_align\n relationships {\n field_button {\n field_link {\n uri\n uri_alias\n }\n field_style\n field_title\n drupal_id\n relationships {\n field_button_background_image {\n ... on media__button_background_image {\n name\n drupal_id\n customLocalFieldMediaImage7 {\n publicURL\n childImageSharp {\n gatsbyImageData(width: 300)\n }\n }\n field_media_image_7 {\n alt\n }\n relationships {\n field_media_image_7 {\n ...FileInfo\n }\n }\n }\n }\n field_file {\n ... on media__audio_file {\n name\n drupal_id\n customLocalFieldMediaAudioFile {\n publicURL\n }\n relationships {\n field_media_audio_file {\n ...FileInfo\n }\n }\n }\n ... on media__document {\n name\n drupal_id\n customLocalFieldMediaFile {\n publicURL\n }\n relationships {\n field_media_file {\n ...FileInfo\n }\n }\n }\n }\n }\n }\n }\n }\n`;\n","import React from 'react';\nimport { graphql } from 'gatsby';\nimport {\n convertBytesToHumanReadableFileSize,\n convertStringToId,\n randomKey,\n} from '../../functions/common.js';\nimport Link from '../elements/link';\n\nexport const DownloadsListParagraph = ({ node }) => {\n let mediaItems = node.relationships?.field_download_media || null,\n showTitle =\n node.field_show_title === undefined || node.field_show_title === null\n ? true\n : node.field_show_title,\n title = node.field_title || 'Download resources',\n wrapperClasses = 'paragraph_downloads_list section--highlight-background';\n\n if (showTitle) {\n wrapperClasses += ' pt-large pb-medium';\n }\n\n // Sort downloads alphabetically, case insensitively\n mediaItems = mediaItems\n ? mediaItems.sort((a, b) => {\n const nameA = a.name ? a.name.toUpperCase() : null;\n const nameB = b.name ? b.name.toUpperCase() : null;\n if (!nameA || !nameB) {\n return nameA.localeCompare(nameB);\n } else {\n new Error('DownloadsListParagraph: Media item name is undefined');\n return null;\n }\n })\n : [];\n\n if (!mediaItems) return ;\n\n const list = mediaItems?.map((item, index) => {\n const fileType =\n Object.keys(item).filter((key) => key.startsWith('customLocalFieldMedia'))[0] || null;\n\n if (!item || fileType === null || item.name === undefined) {\n return null;\n }\n\n const size = (fileType && convertBytesToHumanReadableFileSize(item[fileType]?.size)) || null,\n ext = item[fileType]?.extension.toUpperCase() || null,\n metadata = size && ext ? ` (${ext}, ${size})` : '';\n\n return item[fileType]?.publicURL ? (\n
\n );\n};\n\nexport const fragment = graphql`\n fragment ParagraphEmbedIframe on paragraph__embed_iframe {\n id\n drupal_id\n internal {\n type\n }\n field_external_content_url\n field_fullscreen\n }\n`;\n","import React from 'react';\nimport { graphql } from 'gatsby';\nimport MapEmbed from '../map/MapEmbed';\nimport { useLocation } from '@reach/router';\nimport { validateCoordinates } from '../../functions/common.js';\n\nexport const EmbedMapParagraph = ({ node }) => {\n let embedMap;\n // Default to Satellite style for AMP, regardless of the individual map\n // settings in Drupal\n let style = process.env.GATSBY_PARK === 'amp' ? 'satellite' : node.field_map_style;\n let lat = validateCoordinates([node.field_latitude_number, node.field_longitude_number])[0],\n lon = validateCoordinates([node.field_latitude_number, node.field_longitude_number])[1];\n\n if (node.field_latitude_number != null && node.field_longitude_number != null) {\n // @ts-ignore\n // NOTE: Doesn't support more than one map per page, as ID and title are fixed\n embedMap = (\n \n );\n }\n const location = useLocation();\n let locClass =\n 'mt-xsmall mb-medium ' +\n (location.pathname !== '/access/'\n ? 'responsive-embed responsive-embed--with-poster-image'\n : '');\n let keyid = 'map_' + Math.floor(Math.random() * 49152);\n // move 'grid' control into the map/table output\n return (\n
\n
{embedMap}
\n
\n );\n};\n\nexport const fragment = graphql`\n fragment ParagraphEmbedMap on paragraph__embed_map {\n id\n drupal_id\n internal {\n type\n }\n drupal_internal__id\n field_latitude_number\n field_longitude_number\n field_map_points\n field_map_style\n field_zoom\n }\n`;\n","import React from 'react';\nimport { graphql } from 'gatsby';\nimport Script from '../elements/script';\n\nexport const EmbedScriptParagraph = ({ node }) => {\n const url = node.field_script_src ? node.field_script_src : false,\n manualScript = node.field_manual_script?.value ? node.field_manual_script?.value : null;\n\n // Check if the script is set to load asynchronously or be deferred\n const loadMethod = () => {\n if (node.field_script_loading_method !== 'normal') {\n return node.field_script_loading_method;\n }\n // React ignores null props\n return null;\n };\n\n // Only render the script tag if a src URL is provided. We include then layout\n // wrappers to prevent the layout breakingin case the script inserts content\n // into the page at that point.\n return manualScript || url ? (\n
\n
\n
\n
\n {url && }\n {manualScript && (\n \n )}\n
\n
\n
\n
\n ) : (\n \n );\n};\n\nexport const fragment = graphql`\n fragment ParagraphEmbedScript on paragraph__embed_script {\n id\n drupal_id\n internal {\n type\n }\n field_manual_script {\n value\n }\n field_script_loading_method\n field_script_src\n }\n`;\n","import React from 'react';\nimport { graphql } from 'gatsby';\nimport YoutubeEmbed from '../video/YoutubeEmbed';\n\nexport const EmbedVideoParagraph = ({ node }) => {\n let embedVideoSection;\n let videoEmbedId;\n let videoTitle;\n if (node?.field_video_title?.processed !== null && node?.field_video_id !== null) {\n videoEmbedId = node?.field_video_id;\n videoTitle = node?.field_video_title?.processed;\n embedVideoSection = ;\n return (\n
\n
\n
\n
\n
{embedVideoSection}
\n
\n
\n
\n
\n );\n } else {\n return ;\n }\n};\n\nexport const fragment = graphql`\n fragment ParagraphEmbedVideo on paragraph__embed_video {\n id\n drupal_id\n internal {\n type\n }\n field_video_id\n field_video_title {\n processed\n }\n }\n`;\n","import React from 'react';\nimport { graphql } from 'gatsby';\nimport Link from '../../components/elements/link';\nimport { GatsbyImage, StaticImage, getImage } from 'gatsby-plugin-image';\nimport {\n getTileImageFieldName,\n stripUrlParkPrefix,\n randomKey,\n RawText,\n formatDateToAEST,\n testEnv,\n convertStringToId,\n} from '../../functions/common';\n\nexport const FeedListParagraph = ({ node }) => {\n // feedStyle must align with the values in the corresponding Drupal Paragraph Type.\n // See /admin/structure/paragraphs_type/feed_list/fields/paragraph.feed_list.field_feed_style/storage\n const feedStyle = node.field_feed_style,\n field_hide_caption = node.field_hide_caption,\n feedTitle = node.field_title || node.relationships.field_web_content_collection?.title || null,\n maxItems = node.field_max_items || null,\n sortBy = node.field_sort_by || null;\n\n let feedItems = [];\n\n // TODO: Add sorting options e.g. none, title, updated date, event date etc.\n\n // Check which field is used - Content list, Collection or View, by testing\n // for the presence of child items. If more than one is encountered,\n // prioritise the first to last.\n const feedType = () => {\n let type;\n if (node.relationships.field_content[0]?.drupal_id) {\n type = 'content';\n } else if (node.relationships.field_web_content_collection?.drupal_id) {\n type = 'collection';\n } else if (node.relationships.field_content_view?.drupal_id) {\n type = 'view';\n } else {\n type = undefined;\n }\n return type;\n };\n\n // Build the feed list based on the Feed Style\n const buildFeedList = (\n drupalId: string,\n url: string,\n title: string,\n imageData: object | string,\n imageAlt: string,\n summaryText: string,\n readMoreText: string,\n datePublished: string | null,\n startDate: string | null,\n endDate: string | null,\n where: string,\n when: string | null,\n status: boolean | true\n ) => {\n if (!drupalId || !url || !title) {\n testEnv().devMode\n ? console.warn(\n `[ISSUE - Scripts]: Missing required data for list_links buildFeedList() from 'Feed List' paragraph type in paragraph ID ${node.drupal_id}`\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 let hasImage = imageData ? true : false;\n const linkId = convertStringToId(url);\n const fallbackImg = '../../images/logo-parks-australia-stacked.png';\n // Prioritize the Date fields and fall back to 'When' if empty\n //? NOTE: an empty 'when' arg gets reassigned somehow to 'undefined' as a string, not sure why\n const dateRange = startDate\n ? startDate === endDate\n ? `${formatDateToAEST(startDate)}`\n : `${formatDateToAEST(startDate)} - ${formatDateToAEST(endDate)}`\n : typeof when === 'string' || when !== 'undefined'\n ? when\n : null;\n\n if (feedStyle === 'list_links') {\n return (\n
\n {title}\n
\n );\n } else if (feedStyle === 'list_teasers') {\n // Merge any dates into the Summary text, if any\n summaryText = datePublished\n ? `${formatDateToAEST(datePublished)}
\n );\n } else {\n testEnv().devMode\n ? console.error(`[ERROR - Scripts]: Feed style '${feedStyle}' not recognised in Paragraph ID ${node.id}. Check the permitted values in\n /admin/structure/paragraphs_type/feed_list/fields/paragraph.feed_list.field_feed_style/storage`)\n : null;\n return ;\n }\n };\n\n // If no type is detected, assume the Feed List has no content and do nothing.\n if (feedType() === undefined) {\n return false;\n } else if (feedType() === 'content') {\n // Content items\n\n if (!node.relationships?.field_content) {\n return ;\n }\n feedItems = node.relationships?.field_content?.map((item, index: number) => {\n if (!maxItems || (maxItems && index < maxItems)) {\n const referencedItem = item.relationships?.field_content_item,\n drupalId = referencedItem?.drupal_id,\n // Not all content will necessarily have an alias. If not, don't include a link\n path = stripUrlParkPrefix(referencedItem?.path?.alias) || null,\n title = referencedItem?.title,\n // Publication Date only applies to News Articles\n datePublished = referencedItem?.field_publication_date || null,\n summaryText =\n node.relationships?.field_content[index]?.field_override_summary_text ||\n referencedItem?.body?.summary ||\n referencedItem?.body?.processed ||\n null,\n readMore =\n node.relationships?.field_content[index]?.field_override_read_more_text || 'Read more',\n // This can be extended using a switch statement if more content types require more image fields\n mediaField = getTileImageFieldName(referencedItem?.internal.type),\n imageAlt = referencedItem?.relationships[mediaField]?.field_media_image?.alt || null,\n infoWidget = referencedItem?.relationships?.field_information_widget || {},\n where = () => {\n return infoWidget?.field_where ||\n (infoWidget.hasOwnProperty('field_location') &&\n infoWidget?.field_location?.length > 1)\n ? `${infoWidget?.field_location[0]} and other locations`\n : infoWidget.hasOwnProperty('field_location')\n ? infoWidget?.field_location[0]\n : null;\n },\n when = RawText(infoWidget?.when?.processed) || null,\n status = node.relationships?.field_content[index]?.status;\n\n let imageData =\n getImage(\n referencedItem?.relationships[mediaField]?.customLocalFieldMediaImageThumbnail\n ) ||\n referencedItem?.relationships[mediaField]?.customLocalFieldMediaImageThumbnail\n ?.publicURL ||\n null,\n // Event dates only\n startDate = referencedItem?.field_event_date?.value || null,\n endDate = referencedItem?.field_event_date?.end_value || null;\n\n startDate = startDate ? formatDateToAEST(startDate) : null;\n endDate = endDate ? formatDateToAEST(endDate) : null;\n\n return buildFeedList(\n drupalId,\n path,\n title,\n imageData,\n imageAlt,\n summaryText,\n readMore,\n datePublished,\n startDate,\n endDate,\n where(),\n when,\n status\n );\n }\n });\n } else if (feedType() === 'collection') {\n // Collections\n const collectionItems = node.relationships.field_web_content_collection?.relationships || null;\n // Drill down to content items, as we don't know which content types are used\n // in this Collection we must test the results.\n if (!collectionItems) {\n return ;\n }\n for (let key in collectionItems) {\n if (collectionItems[key] !== null) {\n // TODO: Not tested on Collections containing multiple content types\n const mappedItems = collectionItems[key].map((item, index: number) => {\n if (!maxItems || (maxItems && index < maxItems)) {\n const drupalId = item.drupal_id,\n path = stripUrlParkPrefix(item.path?.alias) || null,\n title = item.title || null,\n // Publication Date only applies to News Articles\n datePublished = item.field_publication_date || null,\n summaryText = item.body?.summary || item.body?.processed || null,\n readMore = 'Read more',\n mediaField = getTileImageFieldName(item.internal.type),\n imageData =\n getImage(item?.relationships?.[mediaField]?.customLocalFieldMediaImageThumbnail) ||\n item?.relationships?.[mediaField]?.customLocalFieldMediaImageThumbnail.publicURL ||\n null,\n imageAlt = item.relationships[mediaField]?.field_media_image?.alt || null,\n when =\n RawText(item?.relationships?.field_information_widget?.when?.processed) || null,\n where = () => {\n return item?.relationships?.field_information_widget?.field_where ||\n item?.relationships?.field_information_widget?.field_location?.length > 1\n ? `${item?.relationships?.field_information_widget?.field_location[0]} and other locations`\n : item?.relationships?.field_information_widget?.field_location[0] || null;\n },\n status = item?.status;\n\n // Event dates only\n let startDate = item.field_event_date?.value || null,\n endDate = item.field_event_date?.end_value || null;\n\n startDate = startDate ? formatDateToAEST(startDate) : null;\n endDate = endDate ? formatDateToAEST(endDate) : null;\n\n return buildFeedList(\n drupalId,\n path,\n title,\n imageData,\n imageAlt,\n summaryText,\n readMore,\n datePublished,\n startDate,\n endDate,\n where(),\n when,\n status\n );\n }\n });\n feedItems.push(mappedItems);\n }\n }\n } else if (feedType() === 'view') {\n // Views\n //! NOTE: Filtering results should be done in the Drupal View, not here\n\n const viewsData = node.relationships?.field_content_view?.relatedCustomDrupalView || null;\n\n if (!viewsData) {\n return ;\n }\n // Loop through the data and build the feed items\n feedItems = viewsData.viewChildren.map((item, index: number) => {\n //ld(item)\n if (!maxItems || (maxItems && index < maxItems)) {\n const drupalId = item.id,\n title = item.title || null,\n // Publication Date only applies to News Articles\n publicationDate = item.field_publication_date || null,\n readMore = item.field_read_more || 'Read more',\n text = item?.body?.summary || item?.body?.processed || null,\n path = stripUrlParkPrefix(item?.path?.alias || item?.path?.url?.uri || null),\n imgMediaType = item.customMediaTileImage\n ? 'customMediaTileImage'\n : 'customMediaInlineImage',\n // Views don't support field_where or field_location as these live in\n // linked paragraphs and therefore not exposed in JSON API\n where = '',\n when = RawText(item?.relationships?.field_information_widget?.when?.processed) || null,\n imageData =\n getImage(\n item?.[imgMediaType]?.relationships?.field_media_image?.relationships\n ?.media__tile_image[0]?.customLocalFieldMediaImageThumbnail\n ) ||\n item?.[imgMediaType]?.relationships?.field_media_image?.relationships\n ?.media__tile_image[0]?.customLocalFieldMediaImageThumbnail?.publicURL ||\n null,\n imageAltText = item?.[imgMediaType]?.field_media_image?.alt || null,\n status = item?.status;\n\n // Event dates only, noting date filtering is done in the View\n let startDate = item.field_event_date?.value\n ? formatDateToAEST(item.field_event_date?.value)\n : null,\n endDate = item.field_event_date?.end_value\n ? formatDateToAEST(item.field_event_date?.end_value)\n : null;\n\n return buildFeedList(\n drupalId,\n path,\n title,\n imageData,\n imageAltText,\n text,\n readMore,\n publicationDate,\n startDate,\n endDate,\n where,\n when,\n status\n );\n }\n });\n } else {\n testEnv().devMode\n ? console.warn('[ISSUE - Scripts]: Unknown Feed type, unable to create feed: ' + feedType())\n : null;\n }\n\n // Clear out any null items that failed to build for any reason\n feedItems = feedItems.filter((item) => item !== null);\n\n // Export the wrapper and feed items within\n return feedItems.length > 0 ? (\n
\n
\n
\n
\n {!field_hide_caption && feedTitle &&
{feedTitle}
}\n {feedStyle === 'list_links' ? (\n
{feedItems}
\n ) : (\n
{feedItems}
\n )}\n
\n
\n
\n
\n ) : (\n \n );\n};\n\nexport const fragment = graphql`\n fragment ParagraphFeedList on paragraph__feed_list {\n id\n drupal_id\n internal {\n type\n }\n field_title\n field_feed_style\n field_hide_caption\n field_max_items\n field_sort_by\n relationships {\n field_content {\n drupal_id\n field_override_read_more_text\n field_override_summary_text\n relationships {\n field_content_item {\n ...NodeDataEvent\n ...NodeDataNews\n ...NodeDataPage\n ...NodeDataPlace\n ...NodeDataPublication\n ...NodeDataRegion\n ...NodeDataTour\n ...NodeDataWildlife\n }\n }\n }\n field_web_content_collection {\n drupal_id\n title\n relationships {\n node__event {\n ...NodeDataEvent\n }\n node__page {\n ...NodeDataPage\n }\n node__place {\n ...NodeDataPlace\n }\n node__tour {\n ...NodeDataTour\n }\n node__wildlife {\n ...NodeDataWildlife\n }\n }\n }\n field_content_view {\n id\n drupal_id\n field_view\n relatedCustomDrupalView {\n ...CustomDataDrupalView\n }\n }\n }\n }\n`;\n","import React from 'react';\nimport { graphql } from 'gatsby';\nimport { GatsbyImage, getImage } from 'gatsby-plugin-image';\nimport { testEnv, roundNumberUpOrDown } from '../../functions/common';\n\nexport const FigureParagraph = ({ node }) => {\n const imageMetadata = node.relationships.field_image,\n targetSize = node.field_size,\n imageBySize = () => {\n // Get the appropriate Gatsby image version based on the selected size\n let img;\n switch (targetSize) {\n case 'small':\n img =\n getImage(node.relationships.field_image?.localImageSmall) ||\n node.relationships.field_image?.localImageSmall?.publicURL ||\n null;\n break;\n case 'medium':\n img =\n getImage(node.relationships.field_image?.localImageMedium) ||\n node.relationships.field_image?.localImageMedium?.publicURL ||\n null;\n break;\n // default to full-width\n default:\n img =\n getImage(node.relationships.field_image?.customLocalFieldMediaImage) ||\n node.relationships.field_image?.customLocalFieldMediaImage?.publicURL ||\n null;\n }\n return img;\n },\n image = imageBySize(),\n sourceWidth = node.relationships.field_image?.field_media_image?.width,\n sourceHeight = node.relationships.field_image?.field_media_image?.height,\n altText = `${imageMetadata?.field_media_image?.alt}. ${\n imageMetadata?.field_attribution?.processed || ''\n }`.trim(),\n figureText = node.field_figure_left_text?.processed,\n // Only use alignment if there is accompanying text\n alignClass = figureText\n ? node.field_align !== 'center'\n ? `figure-float-${node.field_align}`\n : 'figure-center'\n : '',\n hideCaption = node.field_hide_caption || false,\n caption = hideCaption\n ? null\n : node.field_caption?.processed || imageMetadata?.field_caption?.processed || null;\n\n // Prevent build errors if attempting to render an unpublished image\n if (!image || !image.width) {\n testEnv().devMode\n ? console.warn(\n `[ISSUE - Images]: No image found in Figure paragraph ID ${node.drupal_id}.\\nThis can mean you\\'re trying to render an unpublished media item, or the source image does not have a matching File node in GrahpQL\\n`\n )\n : null;\n }\n\n // Determine which set of dimensions to use - image.x sourceX\n // If none are found, ignore width and height, and let the image sit naturally\n\n const dimensions = image\n ? image.width && image.height\n ? { width: image.width, height: image.height, size: targetSize }\n : sourceWidth\n ? { width: sourceWidth, height: sourceHeight, size: targetSize }\n : undefined\n : null;\n\n // TODO: Need to factor in portrait images, as this currently calculates based\n // on width alone.\n // ? Instead of basing the height on the width, do it the other way around for\n //? portrait images. This Should be doable by testing if the width/height\n //? by checking if the width / height ratio is less than or greater than 0.5.\n const scaleImageDimensions = (width, height, size) => {\n // Set the size options for the image\n const sizeOptions = {\n small: 200,\n medium: 400,\n 'full-width': 800,\n };\n\n // If the size is not defined, or width/height is missing, return the\n // original image dimensions\n if (sizeOptions[size] === undefined || !width || !height)\n return {\n width: sourceWidth,\n height: sourceHeight,\n size: '',\n };\n\n if (width < sizeOptions[size]) {\n // If the image is smaller than the requested size, use the image's\n // dimensions because we don't want to upscale due to quality loss.\n return {\n width: width,\n height: height,\n size: size,\n };\n } else {\n // Set the size via the image 'width' attr, but use css for the height\n // We add both attributes to the image so it's valid html\n let calcWidth = sizeOptions[size],\n ratio = calcWidth / width,\n calcHeight = height * ratio;\n\n // If the new height isn't a whole number, round it up or down to\n // avoid possible distortion\n const roundedCalcHeight = roundNumberUpOrDown(calcHeight);\n\n return {\n width: calcWidth,\n height: roundedCalcHeight,\n size: size,\n };\n }\n };\n\n // Render the overriding caption, or the one saved with the Media item itself,\n // or nothing\n const renderCaption = () => {\n if (hideCaption) return;\n let renderCaption = caption;\n return renderCaption;\n };\n\n // Calculate the image dimensions based on the size selected in Drupal\n const scaledDimensions = dimensions\n ? scaleImageDimensions(dimensions.width, dimensions?.height, targetSize)\n : undefined;\n\n // As doesn't accept width/height as props, we instead pass the data\n // in via the gatsbyImageData object's width and height properties\n if (typeof image === 'object' && scaledDimensions) {\n image.width = scaledDimensions.width;\n image.height = scaledDimensions.height;\n }\n\n return image ? (\n
\n
\n
\n
\n \n {/* Allow graceful fallback for Gatsby not correctly saving a childImageSharp version */}\n {typeof image === 'object' ? (\n \n ) : scaledDimensions ? (\n \n ) : (\n // If no dimensions exist, let the image render at its natural width\n \n )}\n\n {renderCaption() ? (\n scaledDimensions ? (\n \n ) : (\n // If no dimensions exist, let the chips land where they may\n \n )\n ) : (\n ''\n )}\n \n\n {figureText && }\n
\n );\n};\n\nexport const fragment = graphql`\n fragment ParagraphQuoteWithImage on paragraph__quote_with_image {\n id\n drupal_id\n internal {\n type\n }\n field_body {\n processed\n }\n field_contact {\n processed\n }\n relationships {\n field_image {\n ...MediaDataTileImage\n }\n }\n }\n`;\n","import React from 'react';\nimport { graphql, useStaticQuery } from 'gatsby';\nimport Link from '../elements/link';\nimport { convertStringToId } from '../../functions/common.js';\n\n/*\n * This component is unique as it requires all other instances if itself on\n * a page to be available each time it is used\n *\n * NOTE: If the Section Heading Link paragraph type in Drupal is allowed to\n * be used on any additional Content Types, they will need to be added in here\n */\n\nexport const SectionHeadingLinksParagraph = ({ node }) => {\n const sectionTitle = convertStringToId(node.field_title),\n sectionTitleId = sectionTitle + '-section',\n sectionParentPageId = node.parent_id,\n allSections = useStaticQuery(graphql`\n query {\n allParagraphSectionHeadingLinks {\n nodes {\n id\n drupal_id\n internal {\n type\n }\n field_hide_section_link\n field_title\n relationships {\n node__place {\n id\n drupal_internal__nid\n }\n node__page {\n id\n drupal_internal__nid\n }\n }\n }\n }\n }\n `);\n\n const SectionItems = () => {\n return allSections.allParagraphSectionHeadingLinks.nodes.map((item) => {\n //! If the Section Link paragraph type is added to any other Content Type\n //! in Drupal, they must be added here\n const parentId = () => {\n if (item.relationships?.node__page) {\n return item.relationships?.node__page[0].drupal_internal__nid;\n }\n if (item.relationships?.node__place) {\n return item.relationships?.node__place[0].drupal_internal__nid;\n }\n return null;\n };\n\n // Only return the Section Link if the parent ID matches\n // the current page, as this gets us a list of all sibling\n // Section Headings on the page\n if (parentId() === parseInt(sectionParentPageId)) {sectionTitle\n const currentSectionLink =\n sectionTitle === convertStringToId(item.field_title) ? 'section__nav-item--active' : '';\n return (\n
\n {item.field_title}\n
\n );\n }\n });\n };\n\n // Give an empty div with the ID if the Section Link should be hidden\n return node.field_hide_section_link ? (\n \n ) : (\n
\n
\n
\n
\n
\n
\n \n
\n
\n
\n
\n
\n
\n );\n};\n\nexport const fragment = graphql`\n fragment ParagraphSectionHeadingLinks on paragraph__section_heading_links {\n id\n drupal_id\n internal {\n type\n }\n field_title\n field_hide_section_link\n parent_id\n }\n`;\n","import React from 'react';\nimport { graphql } from 'gatsby';\n\nexport const TableParagraph = ({ node }) => {\n return node.field_body?.processed ? (\n
\n
\n
\n
\n
\n
\n
\n \n
\n
\n
\n
\n
\n
\n
\n ) : (\n \n );\n};\n\nexport const fragment = graphql`\n fragment ParagraphTable on paragraph__table {\n id\n drupal_id\n internal {\n type\n }\n field_body {\n processed\n }\n }\n`;\n","import React from 'react';\nimport { graphql } from 'gatsby';\n\nexport const TextParagraph = ({ node }) => {\n return node.field_body?.processed ? (\n
\n
\n
\n
\n \n
\n
\n
\n
\n ) : (\n \n );\n};\n\nexport const fragment = graphql`\n fragment ParagraphText on paragraph__text {\n id\n drupal_id\n internal {\n type\n }\n field_body {\n processed\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
This page is marked as '{moderationStateMap[moderationState]}'
\n
\n ) : (\n \n );\n};\n\nexport default AdminInfo;\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