\n
onTextClicked(event)}\n className={classNames(styles.text, styles[langData.direction], styles[langData.font])}\n dangerouslySetInnerHTML={{ __html: text }}\n />\n {shouldShowFootnote && (\n
{\n logButtonClick('translation_footnote_close');\n if (isLoading) {\n hideFootnote();\n } else {\n resetFootnote();\n }\n }}\n onTextClicked={(event) => onTextClicked(event, true)}\n />\n )}\n {subFootnote && (\n \n )}\n {resourceName && (\n \n — {resourceName}\n
\n )}\n \n );\n};\n\nexport default TranslationText;\n","import classNames from 'classnames';\nimport range from 'lodash/range';\nimport { useSelector, shallowEqual } from 'react-redux';\n\nimport cellStyles from './TranslationViewCell.module.scss';\nimport skeletonStyles from './TranslationViewSkeleton.module.scss';\n\nimport verseTextStyles from '@/components/Verse/VerseText.module.scss';\nimport Button, { ButtonSize } from '@/dls/Button/Button';\nimport Skeleton from '@/dls/Skeleton/Skeleton';\nimport useGetQueryParamOrReduxValue from '@/hooks/useGetQueryParamOrReduxValue';\nimport { selectQuranReaderStyles } from '@/redux/slices/QuranReader/styles';\nimport { getFontClassName } from '@/utils/fontFaceHelper';\nimport QueryParam from 'types/QueryParam';\nimport { QuranFont } from 'types/QuranReader';\n\nconst TRANSLATION_TEXT_SAMPLE =\n 'He has revealed to you ˹O Prophet˺ the Book in truth, confirming what came before it, as He revealed the Torah and the Gospel';\nconst TRANSLATION_AUTHOR_SAMPLE = '— Dr. Mustafa Khattab, the Clear Quran';\n\ninterface Props {\n hasActionMenuItems?: boolean;\n}\n\nconst TranslationViewCellSkeleton: React.FC
= ({ hasActionMenuItems = true }) => {\n const { value: selectedTranslations }: { value: number[] } = useGetQueryParamOrReduxValue(\n QueryParam.Translations,\n );\n const { quranFont, quranTextFontScale, translationFontScale, mushafLines } = useSelector(\n selectQuranReaderStyles,\n shallowEqual,\n );\n\n const isTajweedFont = quranFont === QuranFont.Tajweed;\n\n return (\n \n
\n
\n {range(0, 4).map((index) => (\n \n \n \n ))}\n
\n {hasActionMenuItems && (\n
\n \n \n \n
\n )}\n
\n\n {/* We're not using VersePreview as Skeleton's children here \n because it has layout shift problem when loading the font. Which is not ideal for skeleton */}\n
\n
\n
\n {selectedTranslations.map((translation) => (\n
\n \n \n {TRANSLATION_TEXT_SAMPLE}\n \n
\n \n \n {TRANSLATION_AUTHOR_SAMPLE}\n \n
\n \n ))}\n
\n
\n
\n );\n};\n\nexport default TranslationViewCellSkeleton;\n","/**\n * The top -131.6px was calculated based on:\n *\n * 1. the height of emptySpacePlaceholder of navbar (3.6rem).\n * 2. the top padding of the QuranReader container (2rem).\n * 3. the top and bottom margin of the ReadingPreferenceSwitcher container (1.625rem).\n * 4. the top margin of the TranslationView container (1rem).\n *\n * and the total is 8.225rem around 131.6 pixels.\n */\nconst DEFAULT_ROOT_MARGIN = '-131.6px 0px -68% 0px';\nconst OBSERVER_THRESHOLD = 0.1;\nexport const QURAN_READER_OBSERVER_ID = 'quranReaderObserver';\nexport const REFLECTIONS_OBSERVER_ID = 'reflectionsObserver';\n/**\n * the top -115.6px was calculated based on:\n *\n * 1. the height of emptySpacePlaceholder of navbar (3.6rem).\n * 2. the top padding of the QuranReader container (2rem).\n * 3. the top and bottom margin of the ReadingPreferenceSwitcher container (1.625rem).\n *\n * and the total is 7.225rem around 115.6 pixels.\n */\nconst READING_MODE_ROOT_MARGIN = '-115.6px 0px -70% 0px';\n\n/**\n * Get the observer options based on the reading preference.\n *\n * @param {boolean} isReadingPreference\n * @returns {{rootMargin: string, threshold: number | number[]}}\n */\nexport const getOptions = (\n isReadingPreference: boolean,\n): { rootMargin: string; threshold: number | number[] } => ({\n rootMargin: isReadingPreference ? READING_MODE_ROOT_MARGIN : DEFAULT_ROOT_MARGIN,\n threshold: OBSERVER_THRESHOLD,\n});\n\n/**\n * Get the payload that will be dispatched to Redux to set the last read verse.\n *\n * @param {Element} element\n * @returns {{verseKey: string,chapterId: string,page: string,hizb: string}}\n */\nexport const getObservedVersePayload = (\n element: Element,\n): { verseKey: string; chapterId: string; page: string; hizb: string } => ({\n verseKey: element.getAttribute('data-verse-key'),\n chapterId: element.getAttribute('data-chapter-id'),\n page: element.getAttribute('data-page'),\n hizb: element.getAttribute('data-hizb'),\n});\n","/* eslint-disable react/no-multi-comp */\nimport React from 'react';\n\nimport classNames from 'classnames';\nimport dynamic from 'next/dynamic';\nimport useTranslation from 'next-translate/useTranslation';\n\nimport cellStyles from '../QuranReader/TranslationView/TranslationViewCell.module.scss';\n\nimport styles from './OverflowVerseActionsMenuBody.module.scss';\n\nimport Button, { ButtonShape, ButtonSize, ButtonVariant } from '@/dls/Button/Button';\nimport PopoverMenu from '@/dls/PopoverMenu/PopoverMenu';\nimport Spinner from '@/dls/Spinner/Spinner';\nimport OverflowMenuIcon from '@/icons/menu_more_horiz.svg';\nimport { logEvent } from '@/utils/eventLogger';\nimport Verse from 'types/Verse';\n\nconst OverflowVerseActionsMenuBody = dynamic(() => import('./OverflowVerseActionsMenuBody'), {\n ssr: false,\n loading: () => ,\n});\n\ninterface Props {\n verse: Verse;\n isTranslationView?: boolean;\n onActionTriggered?: () => void;\n bookmarksRangeUrl?: string;\n}\n\nconst OverflowVerseActionsMenu: React.FC = ({\n verse,\n isTranslationView = true,\n onActionTriggered,\n bookmarksRangeUrl,\n}) => {\n const { t } = useTranslation('common');\n return (\n \n
\n \n \n \n \n }\n isModal\n isPortalled\n onOpenChange={(open: boolean) => {\n logEvent(\n `${isTranslationView ? 'translation_view' : 'reading_view'}_verse_actions_menu_${\n open ? 'open' : 'close'\n }`,\n );\n }}\n >\n \n \n
\n );\n};\n\nexport default OverflowVerseActionsMenu;\n","var _path;\nfunction _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }\nimport * as React from \"react\";\nvar SvgPlayOutline = function SvgPlayOutline(props) {\n return /*#__PURE__*/React.createElement(\"svg\", _extends({\n width: 24,\n height: 24,\n viewBox: \"0 0 24 24\",\n fill: \"none\",\n xmlns: \"http://www.w3.org/2000/svg\"\n }, props), _path || (_path = /*#__PURE__*/React.createElement(\"path\", {\n d: \"m5 3 14 9-14 9V3Z\",\n stroke: \"currentColor\",\n strokeWidth: 2,\n strokeLinecap: \"round\",\n strokeLinejoin: \"round\"\n })));\n};\nexport default SvgPlayOutline;","import React, { useCallback, useContext, useEffect } from 'react';\n\nimport { useSelector, useSelector as useXstateSelector } from '@xstate/react';\nimport classNames from 'classnames';\nimport useTranslation from 'next-translate/useTranslation';\n\nimport styles from '../QuranReader/TranslationView/TranslationViewCell.module.scss';\n\nimport Spinner from '@/components/dls/Spinner/Spinner';\nimport OnboardingEvent from '@/components/Onboarding/OnboardingChecklist/hooks/OnboardingEvent';\nimport { useOnboarding } from '@/components/Onboarding/OnboardingProvider';\nimport Button, { ButtonShape, ButtonSize, ButtonType, ButtonVariant } from '@/dls/Button/Button';\nimport useGetQueryParamOrXstateValue from '@/hooks/useGetQueryParamOrXstateValue';\nimport PlayIcon from '@/icons/play-outline.svg';\nimport OnboardingGroup from '@/types/OnboardingGroup';\nimport QueryParam from '@/types/QueryParam';\nimport { getChapterData } from '@/utils/chapter';\nimport { logButtonClick } from '@/utils/eventLogger';\nimport { getChapterNumberFromKey, getVerseNumberFromKey } from '@/utils/verse';\nimport DataContext from 'src/contexts/DataContext';\nimport { selectIsVerseLoading } from 'src/xstate/actors/audioPlayer/selectors';\nimport { AudioPlayerMachineContext } from 'src/xstate/AudioPlayerMachineContext';\n\ninterface PlayVerseAudioProps {\n verseKey: string;\n isTranslationView?: boolean;\n onActionTriggered?: () => void;\n}\nconst PlayVerseAudioButton: React.FC = ({\n verseKey,\n isTranslationView = true,\n onActionTriggered,\n}) => {\n const audioService = useContext(AudioPlayerMachineContext);\n const { t } = useTranslation('common');\n const {\n value: reciterId,\n isQueryParamDifferent: reciterQueryParamDifferent,\n }: { value: number; isQueryParamDifferent: boolean } = useGetQueryParamOrXstateValue(\n QueryParam.Reciter,\n );\n const isVisible = useSelector(audioService, (state) => state.matches('VISIBLE'));\n const { isActive, activeStepGroup, nextStep } = useOnboarding();\n\n const isVerseLoading = useXstateSelector(audioService, (state) =>\n selectIsVerseLoading(state, verseKey),\n );\n const chapterId = getChapterNumberFromKey(verseKey);\n const verseNumber = getVerseNumberFromKey(verseKey);\n const chaptersData = useContext(DataContext);\n const chapterData = getChapterData(chaptersData, chapterId.toString());\n\n const onPlayClicked = useCallback(() => {\n // eslint-disable-next-line i18next/no-literal-string\n logButtonClick(`${isTranslationView ? 'translation_view' : 'reading_view'}_play_verse`);\n\n audioService.send({\n type: 'PLAY_AYAH',\n surah: chapterId,\n ayahNumber: verseNumber,\n reciterId: reciterQueryParamDifferent ? reciterId : undefined,\n });\n\n if (onActionTriggered) {\n onActionTriggered();\n }\n\n // if the user clicks on the play button while the onboarding is active, we should automatically go to the next step\n if (isActive && activeStepGroup === OnboardingGroup.READING_EXPERIENCE && isVisible) {\n // audio player menu item step\n nextStep();\n }\n }, [\n activeStepGroup,\n audioService,\n chapterId,\n isActive,\n isTranslationView,\n isVisible,\n nextStep,\n onActionTriggered,\n reciterId,\n reciterQueryParamDifferent,\n verseNumber,\n ]);\n\n useEffect(() => {\n const handlePlayAudioStep = () => {\n onPlayClicked();\n };\n\n window.addEventListener(OnboardingEvent.STEP_AFTER_PLAY_AUDIO_CLICK, handlePlayAudioStep);\n\n return () => {\n window.removeEventListener(OnboardingEvent.STEP_AFTER_PLAY_AUDIO_CLICK, handlePlayAudioStep);\n };\n }, [nextStep, onPlayClicked]);\n\n if (isVerseLoading) {\n return (\n \n );\n }\n\n return (\n \n );\n};\nexport default PlayVerseAudioButton;\n","import { useRef, useImperativeHandle, ForwardedRef } from 'react';\n\nimport * as Dialog from '@radix-ui/react-dialog';\nimport classNames from 'classnames';\nimport { useRouter } from 'next/router';\n\nimport Button, { ButtonShape, ButtonVariant } from '../Button/Button';\n\nimport styles from './ContentModal.module.scss';\n\nimport ContentModalHandles from '@/dls/ContentModal/types/ContentModalHandles';\nimport CloseIcon from '@/icons/close.svg';\nimport { isRTLLocale } from '@/utils/locale';\n\nexport enum ContentModalSize {\n SMALL = 'small',\n MEDIUM = 'medium',\n}\n\ntype ContentModalProps = {\n isOpen?: boolean;\n onClose?: () => void;\n onEscapeKeyDown?: () => void;\n children: React.ReactNode;\n hasCloseButton?: boolean;\n hasHeader?: boolean;\n header?: React.ReactNode;\n innerRef?: ForwardedRef;\n // using innerRef instead of using function forwardRef so we can dynamically load this component https://github.com/vercel/next.js/issues/4957#issuecomment-413841689\n contentClassName?: string;\n size?: ContentModalSize;\n isFixedHeight?: boolean;\n};\n\nconst SCROLLBAR_WIDTH = 15;\n\nconst ContentModal = ({\n isOpen,\n onClose,\n onEscapeKeyDown,\n hasCloseButton,\n children,\n header,\n innerRef,\n contentClassName,\n size = ContentModalSize.MEDIUM,\n isFixedHeight,\n hasHeader = true,\n}: ContentModalProps) => {\n const overlayRef = useRef();\n const { locale } = useRouter();\n\n useImperativeHandle(innerRef, () => ({\n scrollToTop: () => {\n if (overlayRef.current) overlayRef.current.scrollTop = 0;\n },\n }));\n\n /**\n * We need to manually check what the user is targeting. If it lies at the\n * area where the scroll bar is (assuming the scrollbar width is equivalent\n * to SCROLLBAR_WIDTH), then we don't close the Modal, otherwise we do.\n * We also need to check if the current locale is RTL or LTR because the side\n * where the scrollbar is will be different and therefor the value of\n * {e.detail.originalEvent.offsetX} will be different.\n *\n * inspired by {@see https://github.com/radix-ui/primitives/issues/1280#issuecomment-1198248523}\n *\n * @param {any} e\n */\n const onPointerDownOutside = (e: any) => {\n const currentTarget = e.currentTarget as HTMLElement;\n\n const shouldPreventOnClose = isRTLLocale(locale)\n ? e.detail.originalEvent.offsetX < SCROLLBAR_WIDTH // left side of the screen clicked\n : e.detail.originalEvent.offsetX > currentTarget.clientWidth - SCROLLBAR_WIDTH; // right side of the screen clicked\n\n if (shouldPreventOnClose) {\n e.preventDefault();\n return;\n }\n if (onClose) {\n onClose();\n }\n };\n\n return (\n \n \n \n \n {hasHeader && (\n \n {hasCloseButton && (\n \n \n \n )}\n {header}\n
\n )}\n\n {children}
\n \n \n \n \n );\n};\nexport default ContentModal;\n","import React, { ReactNode } from 'react';\n\nimport Popover, { ContentSide } from '@/dls/Popover';\nimport Tooltip from '@/dls/Tooltip';\n\ninterface Props {\n content: ReactNode;\n children: ReactNode | ReactNode[];\n contentSide?: ContentSide;\n tip?: boolean;\n tooltipDelay?: number;\n onOpenChange?: (open: boolean) => void;\n defaultStyling?: boolean;\n isOpen?: boolean;\n triggerStyles?: string;\n isContainerSpan?: boolean;\n}\n\n/**\n * A component that combines the functionality of a Popover and a Tooltip together.\n * This is needed to handle the case when we want to show a Tooltip on mobile but\n * since Tooltip is only hoverable and there is no hovering on mobile devices,\n * we provide the same functionality by using a Popover which handles clicking.\n *\n * @param {Props} props\n * @returns {JSX.Element}\n */\nconst HoverablePopover: React.FC = ({\n content,\n children,\n onOpenChange,\n contentSide = ContentSide.TOP,\n tip = true,\n tooltipDelay = 0,\n defaultStyling = true,\n isOpen,\n triggerStyles,\n isContainerSpan = false,\n}: Props): JSX.Element => (\n \n {children}\n \n }\n tip={tip}\n >\n {content}\n \n);\n\nexport default HoverablePopover;\n","import React, { ReactNode } from 'react';\n\nimport * as RadixPopover from '@radix-ui/react-popover';\nimport classNames from 'classnames';\n\nimport styles from './Popover.module.scss';\n\nexport enum ContentSide {\n TOP = 'top',\n RIGHT = 'right',\n BOTTOM = 'bottom',\n LEFT = 'left',\n}\n\nexport enum ContentAlign {\n START = 'start',\n CENTER = 'center',\n END = 'end',\n}\n\ninterface Props {\n trigger: ReactNode;\n children: ReactNode | ReactNode[];\n onOpenChange?: (open: boolean) => void;\n open?: boolean;\n isModal?: boolean;\n contentSide?: ContentSide;\n contentAlign?: ContentAlign;\n tip?: boolean;\n avoidCollisions?: boolean;\n useTooltipStyles?: boolean;\n defaultStyling?: boolean;\n isPortalled?: boolean;\n triggerStyles?: string;\n contentStyles?: string;\n contentSideOffset?: number;\n isContainerSpan?: boolean;\n}\n\nconst Popover: React.FC = ({\n children,\n trigger,\n onOpenChange,\n open,\n isModal = false,\n contentSide = ContentSide.BOTTOM,\n contentAlign = ContentAlign.CENTER,\n avoidCollisions = true,\n tip = false,\n useTooltipStyles = false,\n defaultStyling = true,\n isPortalled = true,\n contentSideOffset = 2,\n triggerStyles,\n contentStyles,\n isContainerSpan = false,\n}) => {\n const content = (\n \n {children}\n {tip && }\n \n );\n\n const containerChild = (\n \n \n \n {trigger}\n \n \n {isPortalled ? {content} : content}\n \n );\n\n if (isContainerSpan) {\n return (\n {containerChild}\n );\n }\n\n return {containerChild}
;\n};\n\nexport default Popover;\n","var _path, _path2, _path3, _path4;\nfunction _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }\nimport * as React from \"react\";\nvar SvgTranslation = function SvgTranslation(props) {\n return /*#__PURE__*/React.createElement(\"svg\", _extends({\n width: 21,\n height: 21,\n viewBox: \"0 0 21 21\",\n fill: \"none\",\n xmlns: \"http://www.w3.org/2000/svg\"\n }, props), _path || (_path = /*#__PURE__*/React.createElement(\"path\", {\n clipRule: \"evenodd\",\n d: \"M20 10.499V3.374A2.375 2.375 0 0 0 17.625.999H10.5a2.375 2.375 0 0 0-2.375 2.375v7.125a2.375 2.375 0 0 0 2.375 2.375h7.125A2.375 2.375 0 0 0 20 10.499Z\",\n stroke: \"currentColor\",\n strokeWidth: 1.188,\n strokeLinecap: \"round\",\n strokeLinejoin: \"round\"\n })), _path2 || (_path2 = /*#__PURE__*/React.createElement(\"path\", {\n d: \"M6.938 12.878H3.374m2.375-4.75H3.375A2.375 2.375 0 0 0 1 10.503v7.125a2.375 2.375 0 0 0 2.375 2.375h.004l7.125-.012a2.375 2.375 0 0 0 2.371-2.375V15.25L5.75 8.128Z\",\n stroke: \"currentColor\",\n strokeWidth: 1.188,\n strokeLinecap: \"round\",\n strokeLinejoin: \"round\"\n })), _path3 || (_path3 = /*#__PURE__*/React.createElement(\"path\", {\n d: \"M8.719 14.655 7.53 15.843c-.396.395-1.384.989-2.968 1.781\",\n stroke: \"currentColor\",\n strokeWidth: 1.188,\n strokeLinecap: \"round\",\n strokeLinejoin: \"round\"\n })), _path4 || (_path4 = /*#__PURE__*/React.createElement(\"path\", {\n d: \"M16.438 8.124h-4.75m-7.126 4.754c.397 1.384.99 2.375 1.782 2.967.79.593 1.781 1.185 2.968 1.779l-4.75-4.746Zm9.5-9.504L10.5 10.499l3.563-7.125Zm0 0 3.563 7.125-3.563-7.125Z\",\n stroke: \"currentColor\",\n strokeWidth: 1.188,\n strokeLinecap: \"round\",\n strokeLinejoin: \"round\"\n })));\n};\nexport default SvgTranslation;","import { ReactNode } from 'react';\n\nimport styles from './QuranWord.module.scss';\n\nimport { WordByWordType } from 'types/QuranReader';\nimport Word from 'types/Word';\n\n/**\n * Generate the Tooltip content based on the settings.\n *\n * @param {WordByWordType[]} showTooltipFor\n * @param {Word} word\n * @returns {ReactNode}\n */\nconst getTooltipText = (showTooltipFor: WordByWordType[], word: Word): ReactNode => (\n <>\n {showTooltipFor.map((tooltipTextType) => (\n \n {word[tooltipTextType].text}\n
\n ))}\n >\n);\n\nexport default getTooltipText;\n","import VerseTiming from 'types/VerseTiming';\n\nexport const getVerseTimingByVerseKey = (verseKey: string, verseTimings: VerseTiming[]) => {\n return verseTimings.find((verseTiming) => verseTiming.verseKey === verseKey);\n};\n\n// format the number to match the the mp3 file name structure\n// e.g formatNumber(3) => '003'\n// formatNumber(10) => '010'\nconst formatNumber = (num: number) => num.toString().padStart(3, '0');\nexport const QURANCDN_AUDIO_BASE_URL = 'https://audio.qurancdn.com/';\n\n/**\n * Given chapter, verse, and wordLocation. Get the mp3 audio url\n * For example\n * getWordByWordAudioUrl(1,2,1) => `'https://audio.qurancdn.com/wbw/001_002_001.mp3';\n *\n * @param {number} chapter the chapterId\n * @param {number} verse the verse number\n * @param {number} wordLocation the location of the word within a verse\n * @returns {string} audio url\n */\nexport const getWordByWordAudioUrl = (chapter: number, verse: number, wordLocation: number) => {\n const formattedChapter = formatNumber(chapter);\n const formattedVerse = formatNumber(verse);\n const formattedWordLocation = formatNumber(wordLocation);\n\n return `${QURANCDN_AUDIO_BASE_URL}wbw/${formattedChapter}_${formattedVerse}_${formattedWordLocation}.mp3`;\n};\n","import { QURANCDN_AUDIO_BASE_URL } from '@/utils/audio';\nimport { logEvent } from '@/utils/eventLogger';\nimport Word from 'types/Word';\n\nconst playWordAudio = (word: Word) => {\n playWordByWordAudio(`${QURANCDN_AUDIO_BASE_URL}${word.audioUrl}`);\n};\n\nexport default playWordAudio;\n\n/**\n * Given an audio url\n * 1) stop the word by word audio player if it's currently playing\n * 2) pause the main audio player if it's currently playing\n * 3) play the word by word audio\n * 4) resume the main audio player it it's previously was playing\n *\n * Terms\n * - main audio player refer to the audio player in the bottom navbar, this audio player plays the entire chapter\n * - word by word audio player refer to the audio player that play the clicked word\n *\n * @param {string} url\n */\nconst playWordByWordAudio = (url: string) => {\n // stop the audio and remove the DOM if it exists\n if (window.wordByWordAudioPlayerEl) {\n window.wordByWordAudioPlayerEl.pause();\n window.wordByWordAudioPlayerEl.remove();\n window.wordByWordAudioPlayerEl = null;\n }\n\n const removeDOM = () => {\n window.wordByWordAudioPlayerEl.removeEventListener('ended', removeDOM);\n window.wordByWordAudioPlayerEl.remove();\n };\n\n window.wordByWordAudioPlayerEl = new Audio(url);\n logEvent('load_audio_file', { audioUrl: url });\n\n window.wordByWordAudioPlayerEl.play();\n window.wordByWordAudioPlayerEl.addEventListener('ended', removeDOM);\n};\n","import React from 'react';\n\nimport styles from './TranslationsView.module.scss';\n\nimport TranslationText from '@/components/QuranReader/TranslationView/TranslationText';\nimport PlainVerseText from '@/components/Verse/PlainVerseText';\nimport Separator from '@/dls/Separator/Separator';\nimport QuranReaderStyles from '@/redux/types/QuranReaderStyles';\nimport { getVerseWords } from '@/utils/verse';\nimport Translation from 'types/Translation';\nimport Verse from 'types/Verse';\n\ntype Props = {\n verse: Verse;\n quranReaderStyles: QuranReaderStyles;\n};\n\nconst TranslationsView: React.FC = ({ verse, quranReaderStyles }) => {\n return (\n <>\n \n \n \n
\n {verse.translations?.map((translation: Translation) => (\n \n \n
\n ))}\n >\n );\n};\n\nexport default TranslationsView;\n","import { useCallback, useRef, useState } from 'react';\n\nimport classNames from 'classnames';\nimport dynamic from 'next/dynamic';\nimport useTranslation from 'next-translate/useTranslation';\nimport { useSelector } from 'react-redux';\n\nimport styles from './TranslationsButton.module.scss';\n\nimport DataFetcher from '@/components/DataFetcher';\nimport TranslationsView from '@/components/QuranReader/ReadingView/TranslationsView';\nimport TranslationViewCellSkeleton from '@/components/QuranReader/TranslationView/TranslationViewCellSkeleton';\nimport Button, { ButtonShape, ButtonSize, ButtonVariant } from '@/dls/Button/Button';\nimport ContentModalHandles from '@/dls/ContentModal/types/ContentModalHandles';\nimport TranslationsIcon from '@/icons/translation.svg';\nimport { selectQuranReaderStyles } from '@/redux/slices/QuranReader/styles';\nimport { selectSelectedTranslations } from '@/redux/slices/QuranReader/translations';\nimport { getDefaultWordFields, getMushafId } from '@/utils/api';\nimport { makeByVerseKeyUrl } from '@/utils/apiPaths';\nimport { logButtonClick, logEvent } from '@/utils/eventLogger';\nimport { VerseResponse } from 'types/ApiResponses';\nimport Verse from 'types/Verse';\n\nconst ContentModal = dynamic(() => import('@/dls/ContentModal/ContentModal'), {\n ssr: false,\n});\n\ninterface Props {\n verse: Verse;\n onActionTriggered: () => void;\n}\n\nconst CLOSE_POPOVER_AFTER_MS = 200;\n\nconst TranslationsButton: React.FC = ({ verse, onActionTriggered }) => {\n const [isContentModalOpen, setIsContentModalOpen] = useState(false);\n const { t } = useTranslation('common');\n const selectedTranslations = useSelector(selectSelectedTranslations);\n const quranReaderStyles = useSelector(selectQuranReaderStyles);\n const contentModalRef = useRef();\n const translationsQueryKey = makeByVerseKeyUrl(`${verse.chapterId}:${verse.verseNumber}`, {\n words: true,\n translationFields: 'resource_name,language_id',\n translations: selectedTranslations.join(','),\n ...getDefaultWordFields(quranReaderStyles.quranFont),\n ...getMushafId(quranReaderStyles.quranFont, quranReaderStyles.mushafLines),\n });\n\n const renderTranslationsView = useCallback(\n (data: VerseResponse) => {\n if (!data) return ;\n const { verse: responseVerse } = data;\n return ;\n },\n [quranReaderStyles],\n );\n\n const onButtonClicked = () => {\n logButtonClick(\n // eslint-disable-next-line i18next/no-literal-string\n `reading_view_translations_modal_open`,\n );\n setIsContentModalOpen(true);\n };\n\n const onModalClosed = () => {\n // eslint-disable-next-line i18next/no-literal-string\n logEvent(`reading_view_translations_modal_close`);\n setIsContentModalOpen(false);\n setTimeout(() => {\n // we set a really short timeout to close the popover after the modal has been closed to allow enough time for the fadeout css effect to apply.\n onActionTriggered();\n }, CLOSE_POPOVER_AFTER_MS);\n };\n\n const loading = useCallback(() => , []);\n\n return (\n <>\n \n {t('translations')}}\n hasCloseButton\n onClose={onModalClosed}\n onEscapeKeyDown={onModalClosed}\n >\n \n \n >\n );\n};\n\nexport default TranslationsButton;\n","import React from 'react';\n\nimport QuranReflectButton from '../../QuranReflectButton';\n\nimport styles from './WordActionsMenu.module.scss';\n\nimport TranslationsButton from '@/components/QuranReader/ReadingView/TranslationsButton';\nimport TafsirButton from '@/components/QuranReader/TafsirButton';\nimport OverflowVerseActionsMenu from '@/components/Verse/OverflowVerseActionsMenu';\nimport PlayVerseAudioButton from '@/components/Verse/PlayVerseAudioButton';\nimport Word from 'types/Word';\n\ntype Props = {\n word: Word;\n onActionTriggered?: () => void;\n};\n\nconst ReadingViewWordActionsMenu: React.FC = ({ word, onActionTriggered }) => {\n return (\n \n
\n
\n
\n {word?.verse?.timestamps && (\n
\n )}\n
\n \n
\n
\n );\n};\n\nexport default ReadingViewWordActionsMenu;\n","import React, { useState, useCallback } from 'react';\n\nimport { useDispatch } from 'react-redux';\n\nimport ReadingViewWordActionsMenu from '../WordActionsMenu';\n\nimport styles from './WordPopover.module.scss';\n\nimport Popover, { ContentSide } from '@/dls/Popover';\nimport {\n setReadingViewSelectedVerseKey,\n setReadingViewHoveredVerseKey,\n} from '@/redux/slices/QuranReader/readingViewVerse';\nimport { logEvent } from '@/utils/eventLogger';\nimport Word from 'types/Word';\n\ntype Props = {\n word: Word;\n children: React.ReactNode;\n};\n\nconst ReadingViewWordPopover: React.FC = ({ word, children }) => {\n const [isTooltipOpened, setIsTooltipOpened] = useState(false);\n const dispatch = useDispatch();\n\n const onOpenChange = useCallback(\n (isOpen: boolean) => {\n setIsTooltipOpened(isOpen);\n // eslint-disable-next-line i18next/no-literal-string\n logEvent(`reading_view_overflow_menu_${isOpen ? 'open' : 'close'}`);\n dispatch(setReadingViewSelectedVerseKey(isOpen ? word.verseKey : null));\n },\n [dispatch, word.verseKey],\n );\n\n const onHoverChange = useCallback(\n (isHovering: boolean) => {\n dispatch(setReadingViewHoveredVerseKey(isHovering ? word.verseKey : null));\n },\n [dispatch, word.verseKey],\n );\n const onActionTriggered = useCallback(() => {\n onOpenChange(false);\n }, [onOpenChange]);\n\n const onMouseEnter = useCallback(() => {\n onHoverChange(true);\n }, [onHoverChange]);\n\n const onMouseLeave = useCallback(() => {\n onHoverChange(false);\n }, [onHoverChange]);\n\n return (\n \n {children}\n \n }\n tip\n isModal\n open={isTooltipOpened}\n onOpenChange={onOpenChange}\n triggerStyles={styles.trigger}\n contentStyles={styles.content}\n defaultStyling={false}\n >\n