import { Article } from './../../../components/templates/article/Article' import { Box, HStack, SpanBox, VStack } from './../../elements/LayoutPrimitives' import { StyledText } from './../../elements/StyledText' import { ArticleSubtitle, ReaderSavedInfo, } from './../../patterns/ArticleSubtitle' import { theme, ThemeId } from './../../tokens/stitches.config' import { HighlightsLayer } from '../../templates/article/HighlightsLayer' import { Button } from '../../elements/Button' import { useEffect, useState, useRef, useMemo, useCallback } from 'react' import { ReportIssuesModal } from './ReportIssuesModal' import { reportIssueMutation } from '../../../lib/networking/mutations/reportIssueMutation' import { updateTheme, updateThemeLocally } from '../../../lib/themeUpdater' import { ArticleMutations } from '../../../lib/articleActions' import { LabelChip } from '../../elements/LabelChip' import { Label } from '../../../lib/networking/fragments/labelFragment' import { ArticleAttributes, Recommendation, TextDirection, useUpdateItemReadStatus, } from '../../../lib/networking/library_items/useLibraryItems' import { Avatar } from '../../elements/Avatar' import { UserBasicData } from '../../../lib/networking/queries/useGetViewerQuery' type ArticleContainerProps = { viewer: UserBasicData article: ArticleAttributes labels: Label[] articleMutations: ArticleMutations isAppleAppEmbed: boolean highlightBarDisabled: boolean margin?: number fontSize?: number fontFamily?: string lineHeight?: number maxWidthPercentage?: number highContrastText?: boolean showHighlightsModal: boolean highlightOnRelease?: boolean justifyText?: boolean textDirection?: TextDirection setShowHighlightsModal: React.Dispatch> } type RecommendationCommentsProps = { recommendationsWithNotes: Recommendation[] } export interface UpdateTitleEvent extends Event { title?: string } const RecommendationComments = ( props: RecommendationCommentsProps ): JSX.Element => { return ( Comments{' '}  {` ${props.recommendationsWithNotes.length}`} {props.recommendationsWithNotes.map((item) => ( {item.note} ))} ) } export function ArticleContainer(props: ArticleContainerProps): JSX.Element { const [labels, setLabels] = useState(props.labels) const [title, setTitle] = useState(undefined) const [showReportIssuesModal, setShowReportIssuesModal] = useState(false) const [fontSize, setFontSize] = useState(props.fontSize ?? 20) const [highlightOnRelease, setHighlightOnRelease] = useState( props.highlightOnRelease ) // iOS app embed can overide the original margin and line height const [maxWidthPercentageOverride, setMaxWidthPercentageOverride] = useState(null) const [lineHeightOverride, setLineHeightOverride] = useState(null) const [fontFamilyOverride, setFontFamilyOverride] = useState(null) const [highContrastTextOverride, setHighContrastTextOverride] = useState(undefined) const [justifyTextOverride, setJustifyTextOverride] = useState(undefined) const highlightHref = useRef( window.location.hash ? window.location.hash.split('#')[1] : null ) const [textDirection, setTextDirection] = useState( props.textDirection ?? 'LTR' ) const updateFontSize = useCallback( (newFontSize: number) => { setFontSize(newFontSize) }, [setFontSize] ) useEffect(() => { setLabels(props.labels) updateFontSize(props.fontSize ?? 20) }, [props.labels, props.fontSize, updateFontSize]) // Listen for preference change events sent from host apps (ios, macos...) useEffect(() => { interface UpdateLineHeightEvent extends Event { lineHeight?: number } const updateLineHeight = (event: UpdateLineHeightEvent) => { const newLineHeight = event.lineHeight ?? lineHeightOverride ?? 150 if (newLineHeight >= 100 && newLineHeight <= 300) { setLineHeightOverride(newLineHeight) } } interface UpdateHighlightModeEvent extends Event { enableHighlightOnRelease?: string } const updateHighlightMode = (event: UpdateHighlightModeEvent) => { const isEnabled = event.enableHighlightOnRelease === 'on' setHighlightOnRelease(isEnabled) } interface UpdateTextDirectionEvent extends Event { textDirection: TextDirection } const handleUpdateTextDirection = (event: UpdateTextDirectionEvent) => { setTextDirection(event.textDirection) } interface UpdateMaxWidthPercentageEvent extends Event { maxWidthPercentage?: number } const updateMaxWidthPercentage = (event: UpdateMaxWidthPercentageEvent) => { const newMaxWidthPercentage = event.maxWidthPercentage ?? maxWidthPercentageOverride ?? 100 if (newMaxWidthPercentage >= 40 && newMaxWidthPercentage <= 100) { setMaxWidthPercentageOverride(newMaxWidthPercentage) } } interface UpdateFontFamilyEvent extends Event { fontFamily?: string } const updateFontFamily = (event: UpdateFontFamilyEvent) => { const newFontFamily = event.fontFamily ?? fontFamilyOverride ?? props.fontFamily ?? 'inter' setFontFamilyOverride(newFontFamily) } interface UpdateFontContrastEvent extends Event { fontContrast?: 'high' | 'normal' } const handleFontContrastChange = async (event: UpdateFontContrastEvent) => { const highContrast = event.fontContrast == 'high' setHighContrastTextOverride(highContrast) } interface UpdateFontSizeEvent extends Event { fontSize?: number } const handleFontSizeChange = async (event: UpdateFontSizeEvent) => { const newFontSize = event.fontSize ?? 18 if (newFontSize >= 10 && newFontSize <= 48) { updateFontSize(newFontSize) } } interface UpdateThemeEvent extends Event { themeName?: string } const handleThemeChange = async (event: UpdateThemeEvent) => { const newTheme = event.themeName if (newTheme) { updateTheme(newTheme) } } interface UpdateColorModeEvent extends Event { isDark?: string } const updateColorMode = (event: UpdateColorModeEvent) => { const isDark = event.isDark ?? 'false' updateThemeLocally(isDark === 'true' ? ThemeId.Dark : ThemeId.Light) } interface UpdateJustifyText extends Event { justifyText?: boolean } const updateJustifyText = (event: UpdateJustifyText) => { setJustifyTextOverride(event.justifyText ?? false) } interface UpdateLabelsEvent extends Event { labels?: Label[] } const handleUpdateLabels = (event: UpdateLabelsEvent) => { setLabels(event.labels ?? []) } const handleUpdateTitle = (event: UpdateTitleEvent) => { if (event.title) { setTitle(event.title) } } const share = () => { if (navigator.share) { navigator.share({ title: (title ?? props.article.title) + '\n', text: (title ?? props.article.title) + '\n', url: props.article.originalArticleUrl, }) } } const saveReadPosition = () => { console.log('saving read position') } document.addEventListener('saveReadPosition', saveReadPosition) document.addEventListener('updateFontFamily', updateFontFamily) document.addEventListener('updateLineHeight', updateLineHeight) document.addEventListener( 'updateMaxWidthPercentage', updateMaxWidthPercentage ) document.addEventListener('updateTheme', handleThemeChange) document.addEventListener('updateFontSize', handleFontSizeChange) document.addEventListener('updateColorMode', updateColorMode) document.addEventListener( 'handleFontContrastChange', handleFontContrastChange ) document.addEventListener('updateJustifyText', updateJustifyText) document.addEventListener('updateTitle', handleUpdateTitle) document.addEventListener('updateLabels', handleUpdateLabels) document.addEventListener('share', share) document.addEventListener( 'handleAutoHighlightModeChange', updateHighlightMode ) return () => { document.removeEventListener('updateFontFamily', updateFontFamily) document.removeEventListener('updateLineHeight', updateLineHeight) document.removeEventListener( 'updateMaxWidthPercentage', updateMaxWidthPercentage ) document.removeEventListener('updateTheme', handleThemeChange) document.removeEventListener('updateFontSize', handleFontSizeChange) document.removeEventListener('updateColorMode', updateColorMode) document.removeEventListener( 'handleFontContrastChange', handleFontContrastChange ) document.removeEventListener('updateJustifyText', updateJustifyText) document.removeEventListener('updateTitle', handleUpdateTitle) document.removeEventListener('updateLabels', handleUpdateLabels) document.removeEventListener('share', share) document.removeEventListener( 'handleAutoHighlightModeChange', updateHighlightMode ) document.removeEventListener('saveReadPosition', saveReadPosition) } }) const textColorValue = (isHighContrast: boolean) => { return isHighContrast ? theme.colors.readerFontHighContrast.toString() : theme.colors.readerFont.toString() } const justifyTextValue = (isJustified: boolean) => { return isJustified ? 'justify' : 'start' } const appliedFont = (name: string | undefined | null) => { if (name === 'System Default') { return 'unset' } return name } const styles = { fontSize, margin: props.margin ?? 360, maxWidthPercentage: maxWidthPercentageOverride ?? props.maxWidthPercentage, lineHeight: lineHeightOverride ?? props.lineHeight ?? 150, fontFamily: appliedFont(fontFamilyOverride) ?? appliedFont(props.fontFamily) ?? 'inter', readerFontColor: highContrastTextOverride != undefined ? textColorValue(highContrastTextOverride) : textColorValue(props.highContrastText ?? false), readerTableHeaderColor: theme.colors.readerTableHeader.toString(), readerHeadersColor: theme.colors.readerFont.toString(), } const maxWidthStyles = { default: styles.maxWidthPercentage ? `${styles.maxWidthPercentage}%` : 1024 - styles.margin, small: styles.maxWidthPercentage ? `${styles.maxWidthPercentage}%` : `${120 - Math.round((styles.margin * 10) / 100)}%`, } const recommendationsWithNotes = useMemo(() => { return ( props.article.recommendations?.filter((recommendation) => { return recommendation.note }) ?? [] ) }, [props.article.recommendations]) console.log('props.article', props.article) return ( <> {title ?? props.article.title} {labels ? ( {labels?.map((label) => ( ))} ) : null} {recommendationsWithNotes.length > 0 && ( )} {/* {userHasFeature(props.viewer, 'ai-summaries') && ( )} */}
{showReportIssuesModal ? ( { reportIssueMutation({ pageId: props.article.id, itemUrl: props.article.url, reportTypes: ['CONTENT_DISPLAY'], reportComment: comment, }) }} onOpenChange={(open: boolean) => setShowReportIssuesModal(open)} /> ) : null} ) }