{"version":3,"sources":["webpack:///./app/javascript/utilities/viewport.js","webpack:///./app/javascript/utilities/dropdownUtils.js","webpack:///./app/javascript/utilities/debounceAction.js","webpack:///./app/javascript/utilities/http/csrfToken.js","webpack:///./app/javascript/utilities/http/request.js","webpack:///./app/javascript/profilePreviewCards/UserMetadata.jsx","webpack:///./app/javascript/previewCards/feedPreviewCards.jsx"],"names":["isInViewport","element","offsetTop","allowPartialVisibility","boundingRect","getBoundingClientRect","clientHeight","window","innerHeight","document","documentElement","clientWidth","innerWidth","topIsInViewport","top","rightIsInViewport","right","bottomIsInViewport","bottom","leftIsInViewport","left","topIsOutOfViewport","bottomIsOutOfViewport","getDropdownRepositionListener","debounceAction","handleDropdownRepositions","querySelectorAll","classList","remove","isDropdownCurrentlyOpen","style","display","opacity","add","removeProperty","INTERACTIVE_ELEMENTS_QUERY","openDropdown","triggerElementId","dropdownContentId","dropdownContent","getElementById","setAttribute","querySelector","focus","closeDropdown","onClose","initializeDropdown","dropdownContentCloseButtonId","onOpen","triggerButton","keyUpListener","key","getAttribute","onCloseCleanupActions","contains","activeElement","clickOutsideListener","target","matches","removeEventListener","addEventListener","action","time","config","leading","configs","debounce","getCSRFToken","Promise","resolve","reject","i","waitingOnCSRF","setInterval","metaTag","clearInterval","authToken","Honeybadger","notify","JSON","stringify","localStorage","current_user","Error","request","url","options","headers","body","method","csrfToken","restOfOptions","jsonifiedBody","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","setProperty","card_color","checkForPreviewCardDetails","event","getElementsByClassName","populateMissingMetadata","listenForHoveredOrFocusedStoryCards","mainContent","initializeFeedPreviewCards","previewTrigger","dropdownElement","id","initialized","observer","MutationObserver","mutationsList","forEach","mutation","type","observe","childList","subtree","dropdownRepositionListener","InstantClick","on","disconnect"],"mappings":"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","file":"js/145-b64557a2a89c8ec62dfc.chunk.js","sourcesContent":["/**\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 */\nexport const 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 */\nexport const 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 */\nexport const closeDropdown = ({\n triggerElementId,\n dropdownContentId,\n onClose,\n}) => {\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 // Get fresh handle every time, resulting in more streamlined functionality for cypress\n const triggerButton = document.getElementById(triggerElementId);\n if (\n triggerButton &&\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","const MAX_RETRIES = 30;\nconst RETRY_INTERVAL = 250;\n\nexport function getCSRFToken() {\n const promise = new Promise((resolve, reject) => {\n // eslint-disable-next-line consistent-return\n let i = 0;\n const waitingOnCSRF = setInterval(() => {\n const metaTag = document.querySelector(\"meta[name='csrf-token']\");\n i += 1;\n\n if (metaTag) {\n clearInterval(waitingOnCSRF);\n const authToken = metaTag.getAttribute('content');\n return resolve(authToken);\n }\n\n if (i === MAX_RETRIES) {\n clearInterval(waitingOnCSRF);\n Honeybadger.notify(\n `Could not locate CSRF metatag ${JSON.stringify(\n localStorage.current_user,\n )}`,\n );\n return reject(new Error('Could not locate CSRF meta tag on the page.'));\n }\n }, RETRY_INTERVAL);\n });\n return promise;\n}\n","import { getCSRFToken } from './csrfToken';\n\n/**\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"],"sourceRoot":""}