{"version":3,"sources":["webpack:///./app/javascript/utilities/http/request.js","webpack:///./app/javascript/profilePreviewCards/UserMetadata.jsx","webpack:///./app/javascript/previewCards/feedPreviewCards.jsx","webpack:///./app/javascript/utilities/viewport.js","webpack:///./app/javascript/utilities/dropdownUtils.js","webpack:///./app/javascript/utilities/debounceAction.js"],"names":["request","url","options","headers","body","method","csrfToken","getCsrfToken","restOfOptions","jsonifiedBody","JSON","stringify","fetchOptions","Accept","credentials","fetch","UserMetadata","memo","email","location","summary","created_at","education","work","joinedOnDate","Date","joinedOnDateString","Intl","DateTimeFormat","navigator","language","day","month","year","format","className","class","href","datetime","cachedAuthorMetadata","metadataPlaceholder","dataset","authorId","fetched","previouslyFetchedAuthorMetadata","renderMetadata","response","authorMetadata","json","metadata","placeholder","container","parentElement","render","closest","style","setProperty","card_color","checkForPreviewCardDetails","event","target","classList","contains","getElementsByClassName","populateMissingMetadata","listenForHoveredOrFocusedStoryCards","mainContent","document","getElementById","addEventListener","initializeFeedPreviewCards","querySelectorAll","previewTrigger","dropdownContentId","getAttribute","dropdownElement","initializeDropdown","triggerElementId","id","onOpen","add","onClose","remove","initialized","observer","MutationObserver","mutationsList","forEach","mutation","type","observe","childList","subtree","dropdownRepositionListener","getDropdownRepositionListener","InstantClick","on","disconnect","removeEventListener","window","isInViewport","element","offsetTop","allowPartialVisibility","boundingRect","getBoundingClientRect","clientHeight","innerHeight","documentElement","clientWidth","innerWidth","topIsInViewport","top","rightIsInViewport","right","bottomIsInViewport","bottom","leftIsInViewport","left","topIsOutOfViewport","bottomIsOutOfViewport","debounceAction","handleDropdownRepositions","isDropdownCurrentlyOpen","display","opacity","removeProperty","INTERACTIVE_ELEMENTS_QUERY","closeDropdown","dropdownContent","setAttribute","dropdownContentCloseButtonId","triggerButton","keyUpListener","key","onCloseCleanupActions","focus","activeElement","clickOutsideListener","matches","querySelector","openDropdown","action","time","config","leading","configs","debounce"],"mappings":"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","file":"js/108-c4486501da1351fbb82e.chunk.js","sourcesContent":["/**\n * Generic request with all the default headers required by the application.\n *\n * @example\n * import { request } from '@utilities/http';\n *\n * const response = await request('/notification_subscriptions/Article/26')\n *\n * Note:\n * The body option will typically be passed in as a JavaScript object.\n * A check is performed for this and automatically convert it to JSON if necessary.\n *\n * Requests send JSON by default but this can be easily overridden by adding\n * the Accept and Content-Type headers to the request options.\n *\n * The default method is GET.\n *\n * @param {string} url The URL to make the request to.\n * @param {RequestInit} [options={}] The request options.\n *\n * @return {Promise} the response\n */\nexport async function request(url, options = {}) {\n const {\n headers,\n body,\n method = 'GET',\n csrfToken = await getCsrfToken(),\n // These are any other options that might be passed in e.g. keepalive\n ...restOfOptions\n } = options;\n\n // There should never be a scenario where null is passed as the body,\n // but if ever there is, this logic should change.\n const jsonifiedBody = {\n body: body && typeof body !== 'string' ? JSON.stringify(body) : body,\n };\n\n const fetchOptions = {\n method,\n headers: {\n Accept: 'application/json',\n 'X-CSRF-Token': csrfToken,\n 'Content-Type': 'application/json',\n ...headers,\n },\n credentials: 'same-origin',\n ...jsonifiedBody,\n ...restOfOptions,\n };\n\n return fetch(url, fetchOptions);\n}\n","import { h, Fragment } from 'preact';\nimport { memo } from 'preact/compat';\n\n/**\n * Component which renders the user metadata detail in a profile preview card.\n *\n * @param {object} props\n * @param {string} props.email The user's email (if set to be publicly displayed)\n * @param {string} props.location The user's location\n * @param {string} props.created_at The user's join date string\n * @param {string} props.education The user's education detail\n * @param {string} props.work The user's work details\n */\nexport const UserMetadata = memo(\n ({ email, location, summary, created_at, education, work }) => {\n const joinedOnDate = new Date(created_at);\n const joinedOnDateString = new Intl.DateTimeFormat(\n navigator.language || 'default',\n {\n day: 'numeric',\n month: 'long',\n year: 'numeric',\n },\n ).format(joinedOnDate);\n\n return (\n \n {summary &&
{summary}
}\n
\n \n
\n
\n );\n },\n);\n","import { h, render } from 'preact';\nimport { UserMetadata } from '../profilePreviewCards/UserMetadata';\nimport {\n initializeDropdown,\n getDropdownRepositionListener,\n} from '@utilities/dropdownUtils';\nimport { request } from '@utilities/http/request';\n\nconst cachedAuthorMetadata = {};\n\nasync function populateMissingMetadata(metadataPlaceholder) {\n const { authorId, fetched } = metadataPlaceholder.dataset;\n\n // If the metadata is already being fetched, do nothing\n if (fetched) {\n return;\n }\n metadataPlaceholder.dataset.fetched = 'true';\n\n const previouslyFetchedAuthorMetadata = cachedAuthorMetadata[authorId];\n\n if (previouslyFetchedAuthorMetadata) {\n renderMetadata(previouslyFetchedAuthorMetadata, metadataPlaceholder);\n } else {\n const response = await request(`/profile_preview_cards/${authorId}`);\n const authorMetadata = await response.json();\n\n cachedAuthorMetadata[authorId] = authorMetadata;\n renderMetadata(authorMetadata, metadataPlaceholder);\n }\n}\n\nfunction renderMetadata(metadata, placeholder) {\n const container = placeholder.parentElement;\n\n render(, container, placeholder);\n\n container\n .closest('.profile-preview-card__content')\n .style.setProperty('--card-color', metadata.card_color);\n}\n\nfunction checkForPreviewCardDetails(event) {\n const { target } = event;\n\n if (target.classList.contains('profile-preview-card__trigger')) {\n const metadataPlaceholder = target.parentElement.getElementsByClassName(\n 'author-preview-metadata-container',\n )[0];\n\n if (metadataPlaceholder) {\n // User is within one of the story cards - and the metadata has not been fetched yet\n populateMissingMetadata(metadataPlaceholder);\n }\n }\n}\n\nexport function listenForHoveredOrFocusedStoryCards() {\n const mainContent = document.getElementById('main-content');\n\n mainContent.addEventListener('mouseover', checkForPreviewCardDetails);\n mainContent.addEventListener('focusin', checkForPreviewCardDetails);\n}\n\nexport function initializeFeedPreviewCards() {\n // Select all preview card triggers that haven't already been initialized\n const allPreviewCardTriggers = document.querySelectorAll(\n 'button[id^=story-author-preview-trigger]:not([data-initialized])',\n );\n\n for (const previewTrigger of allPreviewCardTriggers) {\n const dropdownContentId = previewTrigger.getAttribute('aria-controls');\n const dropdownElement = document.getElementById(dropdownContentId);\n\n if (dropdownElement) {\n initializeDropdown({\n triggerElementId: previewTrigger.id,\n dropdownContentId,\n onOpen: () => dropdownElement?.classList.add('showing'),\n onClose: () => dropdownElement?.classList.remove('showing'),\n });\n\n previewTrigger.dataset.initialized = true;\n }\n }\n}\n\nconst observer = new MutationObserver((mutationsList) => {\n mutationsList.forEach((mutation) => {\n if (mutation.type === 'childList') {\n initializeFeedPreviewCards();\n }\n });\n});\n\nif (document.getElementById('index-container')) {\n observer.observe(document.getElementById('index-container'), {\n childList: true,\n subtree: true,\n });\n}\n\n// Preview card dropdowns reposition on scroll\nconst dropdownRepositionListener = getDropdownRepositionListener();\ndocument.addEventListener('scroll', dropdownRepositionListener);\n\nInstantClick.on('change', () => {\n observer.disconnect();\n document.removeEventListener('scroll', dropdownRepositionListener);\n});\n\nwindow.addEventListener('beforeunload', () => {\n observer.disconnect();\n document.removeEventListener('scroll', dropdownRepositionListener);\n});\n","/**\n * Checks if an element is visible in the viewport\n *\n * @example\n * const element = document.getElementById('element');\n * isInViewport({element, allowPartialVisibility = true}); // true or false\n *\n * @param {HTMLElement} element - The HTML element to check\n * @param {number} [offsetTop=0] - Part of the screen to ignore counting from the top\n * @param {boolean} [allowPartialVisibility=false] - A boolean to flip the check between partial or completely visible in the viewport\n * @returns {boolean} isInViewport - true if the element is visible in the viewport\n */\nexport function isInViewport({\n element,\n offsetTop = 0,\n allowPartialVisibility = false,\n}) {\n const boundingRect = element.getBoundingClientRect();\n const clientHeight =\n window.innerHeight || document.documentElement.clientHeight;\n const clientWidth = window.innerWidth || document.documentElement.clientWidth;\n const topIsInViewport =\n boundingRect.top <= clientHeight && boundingRect.top >= offsetTop;\n const rightIsInViewport =\n boundingRect.right >= 0 && boundingRect.right <= clientWidth;\n const bottomIsInViewport =\n boundingRect.bottom >= offsetTop && boundingRect.bottom <= clientHeight;\n const leftIsInViewport =\n boundingRect.left <= clientWidth && boundingRect.left >= 0;\n const topIsOutOfViewport = boundingRect.top <= offsetTop;\n const bottomIsOutOfViewport = boundingRect.bottom >= clientHeight;\n const elementSpansEntireViewport =\n topIsOutOfViewport && bottomIsOutOfViewport;\n\n if (allowPartialVisibility) {\n return (\n (topIsInViewport || bottomIsInViewport || elementSpansEntireViewport) &&\n (leftIsInViewport || rightIsInViewport)\n );\n }\n return (\n topIsInViewport &&\n bottomIsInViewport &&\n leftIsInViewport &&\n rightIsInViewport\n );\n}\n","import { isInViewport } from '@utilities/viewport';\nimport { debounceAction } from '@utilities/debounceAction';\n\n/**\n * Helper function designed to be used on scroll to detect when dropdowns should switch from dropping downwards/upwards.\n * The action is debounced since scroll events are usually fired several at a time.\n *\n * @returns {Function} a debounced function that handles the repositioning of dropdowns\n * @example\n *\n * document.addEventListener('scroll', getDropdownRepositionListener());\n */\nexport const getDropdownRepositionListener = () =>\n debounceAction(handleDropdownRepositions);\n\n/**\n * Checks for all dropdowns on the page which have the attribute 'data-repositioning-dropdown', signalling\n * they should dynamically change between dropping downwards or upwards, depending on viewport position.\n *\n * Any dropdowns not fully in view when dropping down will be switched to dropping upwards.\n */\nconst handleDropdownRepositions = () => {\n // Select all of the dropdowns which should reposition\n const allRepositioningDropdowns = document.querySelectorAll(\n '[data-repositioning-dropdown]',\n );\n\n for (const element of allRepositioningDropdowns) {\n // Default to dropping downwards\n element.classList.remove('reverse');\n\n const isDropdownCurrentlyOpen = element.style.display === 'block';\n\n if (!isDropdownCurrentlyOpen) {\n // We can't determine position on an element with display:none, so we \"show\" the dropdown with 0 opacity very temporarily\n element.style.opacity = 0;\n element.style.display = 'block';\n }\n\n if (!isInViewport({ element })) {\n // If the element isn't fully visible when dropping down, reverse the direction\n element.classList.add('reverse');\n }\n\n if (!isDropdownCurrentlyOpen) {\n // Revert the temporary changes to determine position\n element.style.removeProperty('display');\n element.style.removeProperty('opacity');\n }\n }\n};\n\n/**\n * Helper query string to identify interactive/focusable HTML elements\n */\nconst INTERACTIVE_ELEMENTS_QUERY =\n 'button, [href], input:not([type=\"hidden\"]), select, textarea, [tabindex=\"0\"]';\n\n/**\n * Open the given dropdown, updating aria attributes, and focusing the first interactive element\n *\n * @param {Object} args\n * @param {string} args.triggerElementId The id of the button which activates the dropdown\n * @param {string} args.dropdownContent The id of the dropdown content element\n */\nconst openDropdown = ({ triggerElementId, dropdownContentId }) => {\n const dropdownContent = document.getElementById(dropdownContentId);\n const triggerElement = document.getElementById(triggerElementId);\n\n triggerElement.setAttribute('aria-expanded', 'true');\n\n // Style set inline to prevent specificity issues\n dropdownContent.style.display = 'block';\n\n // Send focus to the first suitable element\n dropdownContent.querySelector(INTERACTIVE_ELEMENTS_QUERY)?.focus();\n};\n\n/**\n * Close the given dropdown, updating aria attributes\n *\n * @param {Object} args\n * @param {string} args.triggerElementId The id of the button which activates the dropdown\n * @param {string} args.dropdownContent The id of the dropdown content element\n * @param {Function} args.onClose Optional function for any side-effects which should occur on dropdown close\n */\nconst closeDropdown = ({ triggerElementId, dropdownContentId, onClose }) => {\n const dropdownContent = document.getElementById(dropdownContentId);\n\n if (!dropdownContent) {\n // Component may have unmounted\n return;\n }\n\n document\n .getElementById(triggerElementId)\n ?.setAttribute('aria-expanded', 'false');\n\n // Remove the inline style added when we opened the dropdown\n dropdownContent.style.removeProperty('display');\n\n onClose?.();\n};\n\n/**\n * A helper function to initialize dropdown behaviors. This function attaches open/close click and keyup listeners,\n * and makes sure relevant aria properties and keyboard focus are updated.\n *\n * @param {Object} args\n * @param {string} args.triggerButtonElementId The ID of the button which triggers the dropdown open/close behavior\n * @param {string} args.dropdownContentId The ID of the dropdown content which should open/close on trigger button press\n * @param {string} args.dropdownContentCloseButtonId Optional ID of any button within the dropdown content which should close the dropdown\n * @param {Function} args.onClose An optional callback for when the dropdown is closed. This can be passed to execute any side-effects required when the dropdown closes.\n * @param {Function} args.onOpen An optional callback for when the dropdown is opened. This can be passed to execute any side-effects required when the dropdown opens.\n *\n * @returns {{closeDropdown: Function}} Object with callback to close the initialized dropdown\n */\nexport const initializeDropdown = ({\n triggerElementId,\n dropdownContentId,\n dropdownContentCloseButtonId,\n onClose,\n onOpen,\n}) => {\n const triggerButton = document.getElementById(triggerElementId);\n const dropdownContent = document.getElementById(dropdownContentId);\n\n if (!triggerButton || !dropdownContent) {\n // The required props haven't been provided, do nothing\n return;\n }\n\n // Ensure default values have been applied\n triggerButton.setAttribute('aria-expanded', 'false');\n triggerButton.setAttribute('aria-controls', dropdownContentId);\n triggerButton.setAttribute('aria-haspopup', 'true');\n\n const keyUpListener = ({ key }) => {\n if (key === 'Escape') {\n // Close the dropdown and return focus to the trigger button to prevent focus being lost\n const isCurrentlyOpen =\n triggerButton.getAttribute('aria-expanded') === 'true';\n if (isCurrentlyOpen) {\n closeDropdown({\n triggerElementId,\n dropdownContentId,\n onClose: onCloseCleanupActions,\n });\n triggerButton.focus();\n }\n } else if (key === 'Tab') {\n // Close the dropdown if the user has tabbed away from it\n const isInsideDropdown = dropdownContent?.contains(\n document.activeElement,\n );\n if (!isInsideDropdown) {\n closeDropdown({\n triggerElementId,\n dropdownContentId,\n onClose: onCloseCleanupActions,\n });\n }\n }\n };\n\n // Close the dropdown if user has clicked outside\n const clickOutsideListener = ({ target }) => {\n if (\n target !== triggerButton &&\n !dropdownContent.contains(target) &&\n !triggerButton.contains(target)\n ) {\n closeDropdown({\n triggerElementId,\n dropdownContentId,\n onClose: onCloseCleanupActions,\n });\n\n // If the user did not click on another interactive item, return focus to the trigger\n if (!target.matches(INTERACTIVE_ELEMENTS_QUERY)) {\n triggerButton.focus();\n }\n }\n };\n\n // Any necessary side effects required on dropdown close\n const onCloseCleanupActions = () => {\n onClose?.();\n document.removeEventListener('keyup', keyUpListener);\n document.removeEventListener('click', clickOutsideListener);\n };\n\n // Add the main trigger button toggle functionality\n triggerButton.addEventListener('click', () => {\n if (\n document\n .getElementById(triggerElementId)\n ?.getAttribute('aria-expanded') === 'true'\n ) {\n closeDropdown({\n triggerElementId,\n dropdownContentId,\n onClose: onCloseCleanupActions,\n });\n } else {\n openDropdown({\n triggerElementId,\n dropdownContentId,\n });\n onOpen?.();\n\n document.addEventListener('keyup', keyUpListener);\n document.addEventListener('click', clickOutsideListener);\n }\n });\n\n if (dropdownContentCloseButtonId) {\n // The dropdown content has a 'close' button inside that we also need to handle\n document\n .getElementById(dropdownContentCloseButtonId)\n ?.addEventListener('click', () => {\n closeDropdown({\n triggerElementId,\n dropdownContentId,\n onClose: onCloseCleanupActions,\n });\n\n document.getElementById(triggerElementId)?.focus();\n });\n }\n\n return {\n closeDropdown: () => {\n closeDropdown({\n triggerElementId,\n dropdownContentId,\n onClose: onCloseCleanupActions,\n });\n },\n };\n};\n","import debounce from 'lodash.debounce';\n\n/**\n * A util function to wrap any action with lodash's `debounce` (https://lodash.com/docs/#debounce).\n * To use this util, wrap it in the util like so: debounceAction(onSearchBoxType.bind(this));\n *\n * By default, this util uses a default time of 300ms, and includes a default config of `{ leading: false }`.\n * These values can be overridden: debounceAction(this.onSearchBoxType.bind(this), { time: 100, config: { leading: true }});\n *\n *\n * @param {Function} action - The function that should be wrapped with `debounce`.\n * @param {Number} [time=300] - The number of milliseconds to wait.\n * @param {Object} [config={ leading: false }] - Any configuration for the debounce function.\n *\n * @returns {Function} A function wrapped in `debounce`.\n */\nexport function debounceAction(\n action,\n { time = 300, config = { leading: false } } = {},\n) {\n const configs = { ...config };\n return debounce(action, time, configs);\n}\n"],"sourceRoot":""}