/** * WordPress dependencies */ import { useDispatch, useSelect } from '@safe-wordpress/data'; import { useEffect, useMemo, useRef } from '@safe-wordpress/element'; /** * External dependencies */ import { head, last } from 'lodash'; import { store as NC_DATA } from '@nelio-content/data'; import { computeSocialMessageText, getAllUrls, getBaseDatetime, getSocialMessageSchedule, } from '@nelio-content/utils'; import type { Maybe, Post, SharedLink, SharedLinkStatus, SocialMessage, SocialNetworkName, SocialProfile, Url, } from '@nelio-content/types'; /** * Internal dependencies */ import { store as NC_SOCIAL_EDITOR } from '../store'; import { useRelatedPost } from './post'; import { useMaxChars } from './text'; export const usePreview = (): [ boolean, ( v: boolean ) => void ] => { const isVisible = useSelect( ( select ) => select( NC_SOCIAL_EDITOR ).isPreviewVisible(), [] ); const { showPreview } = useDispatch( NC_SOCIAL_EDITOR ); return [ isVisible, showPreview ]; }; export const usePreviewAttributes = (): Omit< PreviewAttributes, 'activeProfile' > & Pick< Partial< PreviewAttributes >, 'activeProfile' > => { const limit = useMaxChars(); const state = useSelect( ( select ) => { const data = select( NC_DATA ); const s = select( NC_SOCIAL_EDITOR ); const activeNetwork = s.getActiveSocialNetwork(); return { getSocialProfile: data.getSocialProfile, activeNetwork, activeProfileId: s.getActiveSocialProfile(), dateType: s.getDateType(), dateValue: s.getDateValue(), timeType: s.getTimeType(), timeValue: s.getTimeValue(), image: s.getImageUrl( activeNetwork ), imageAltText: s.getImageAltText( activeNetwork ), video: s.getVideoUrl(), post: s.getPost(), text: s.getText(), type: s.getMessageTypeInNetwork( activeNetwork ), plurkQualifier: s.getPlurkQualifier(), }; }, [] ); return useMemo( () => { const schedule = getSocialMessageSchedule( { baseDatetime: getBaseDatetime( state.post, state.dateType ), dateValue: state.dateValue, timeType: state.timeType, timeValue: state.timeValue, } ); let activeProfile; if ( state.activeProfileId ) { activeProfile = state.getSocialProfile( state.activeProfileId ); } return { activeProfile, dateValue: state.dateValue, image: state.image, imageAltText: state.imageAltText, video: state.video, postId: state.post?.id, schedule, textComputed: computeSocialMessageText( limit, state.post, state.text ), timeValue: state.timeValue, type: state.type, plurkQualifier: state.plurkQualifier, }; }, [ limit, state ] ); }; export const useSharedLinkAttributes = ( linkPosition: 'first' | 'last', network?: SocialNetworkName ): { readonly sharedLinkStatus: 'no-url' | SharedLinkStatus; readonly sharedLink?: SharedLink; } => { const url = useRelevantLink( linkPosition ); const [ post ] = useRelatedPost(); const isPostUrl = !! post && areUrlsEqual( url, post?.permalink ); const extLinkStatus = useSelect( ( select ) => url && ! isPostUrl ? select( NC_DATA ).getSharedLinkStatus( url ) : undefined, [ url, isPostUrl ] ); const extLinkData = useSelect( ( select ) => url && ! isPostUrl ? select( NC_DATA ).getSharedLinkData( url ) : undefined, [ url, isPostUrl ] ); if ( ! url ) { return { sharedLinkStatus: 'no-url', }; } if ( isPostUrl ) { return { sharedLinkStatus: 'ready', sharedLink: postToSharedLink( post, network ), }; } return { sharedLinkStatus: extLinkStatus || 'loading', sharedLink: extLinkData, }; }; export const useSharedLinkLoaderEffect = ( urls: ReadonlyArray< Url > ): void => { const timerRef = useRef< ReturnType< typeof setTimeout > | null >( null ); const { loadLink } = useDispatch( NC_DATA ); const [ post ] = useRelatedPost(); useEffect( () => { if ( timerRef.current ) { clearTimeout( timerRef.current ); timerRef.current = null; } timerRef.current = setTimeout( () => urls .filter( ( url ) => ! post?.permalink || ! areUrlsEqual( url, post?.permalink ) ) .forEach( ( url ) => void loadLink( url ) ), 1000 ); return () => { if ( timerRef.current ) { clearTimeout( timerRef.current ); timerRef.current = null; } }; }, [ urls, loadLink, post?.permalink ] ); }; // ===== // TYPES // ===== export type PreviewAttributes = Pick< SocialMessage, | 'dateValue' | 'image' | 'imageAltText' | 'video' | 'postId' | 'schedule' | 'textComputed' | 'timeValue' | 'type' | 'plurkQualifier' > & { readonly activeProfile: SocialProfile; }; // ======= // HELPERS // ======= const useRelevantLink = ( linkPosition: 'first' | 'last' ) => { const limit = useMaxChars(); const { post, text } = useSelect( ( select ) => { const s = select( NC_SOCIAL_EDITOR ); return { post: s.getPost(), text: s.getText() }; }, [] ); const url = useMemo( () => { const textComputed = computeSocialMessageText( limit, post, text ); const urls = getAllUrls( textComputed ); return linkPosition === 'first' ? head( urls ) : last( urls ); }, [ limit, post, text, linkPosition ] ); return url; }; const postToSharedLink = ( post: Post, network?: SocialNetworkName ): SharedLink => ( { author: post.authorName, date: post.date || '', domain: ( post.permalink || '' ) .replace( /^https?:\/\//, '' ) .replace( /\/.*$/, '' ), email: '', excerpt: post.excerpt, image: ( !! network && post.networkImages[ network ] ) || post.imageSrc || '', permalink: post.permalink, responseCode: 200, title: post.title, twitter: '', } ); const areUrlsEqual = ( a: Maybe< Url >, b: Maybe< Url > ) => !! a && !! b && a.replace( /^https?:\/\//, 'http://' ) === b.replace( /^https?:\/\//, 'http://' );