/** * WordPress dependencies */ import apiFetch from '@safe-wordpress/api-fetch'; import { select, dispatch, subscribe } from '@safe-wordpress/data'; import { render } from '@safe-wordpress/element'; /** * External dependencies */ import { filter, isEqual, map, trim } from 'lodash'; import { getDisabledProfiles, processHtml, store as NC_EDIT_POST, } from '@nelio-content/edit-post'; import { store as NC_DATA } from '@nelio-content/data'; import { store as NC_SOCIAL_EDITOR } from '@nelio-content/social-message-editor'; import { getLinks, isEmpty } from '@nelio-content/utils'; import type { AuthorId, AutoShareEndMode, AwsPost, Dict, EditingPost, EditorialReference, Maybe, PostId, Url, } from '@nelio-content/types'; /** * Internal dependencies */ import { addListeners } from './listeners'; // ======= // EXPORTS // ======= export type Args = { readonly attributes: { readonly externalFeatImage: { readonly url: Url; readonly alt: string; }; readonly followers: ReadonlyArray< AuthorId >; readonly references: ReadonlyArray< EditorialReference >; }; readonly postId: PostId; readonly settings: Settings; }; export type Settings = { readonly isClassicEditor?: boolean; readonly isElementorEditor?: boolean; readonly nonce: string; readonly qualityAnalysis: QualityAnalysisSettings; readonly autoShareEndModes: ReadonlyArray< AutoShareEndMode >; readonly shouldAuthorBeFollower: boolean; }; export type QualityAnalysisSettings = { readonly canImageBeAutoSet: boolean; readonly isFullyIntegrated: boolean; readonly isYoastIntegrated: boolean; readonly supportsFeatImage: boolean; }; export async function init( args: Args ): Promise< void > { const post = await loadPostFromWordPress( args.postId ); await initPost( { ...post, date: 'none' === post.date ? false : post.date, followers: args.attributes.followers, statistics: { engagement: {}, pageviews: {}, }, } ); setEditorType( args.settings.isClassicEditor, args.settings.isElementorEditor ); addTinyMCEGlobal(); initAutoShareEndModes( args.settings.autoShareEndModes ); initDefaultFollowers( args.settings.shouldAuthorBeFollower ); initExternalFeaturedImage( args.attributes.externalFeatImage ); initReferences( args.attributes.references ); customizeQualityAnalysis( args.settings.qualityAnalysis ); addListeners(); keepRelatedItemsSynchronized(); } export function listenToEditPostStore( callback: ( values: Dict ) => void ): void { let prevValues = {}; // eslint-disable-next-line @typescript-eslint/no-unsafe-call subscribe( () => { const { getAutoShareEndMode, getAutomationSources, getExternalFeaturedImageAlt, getExternalFeaturedImageUrl, getNetworkImageIds, getPost, getQueryArgs, getSeries, getSuggestedReferences, isAutoShareEnabled, } = select( NC_EDIT_POST ); const post = getPost(); const { highlights } = processHtml( post.content ); const values = { autoShareEndMode: getAutoShareEndMode(), automationSources: getAutomationSources(), efiAlt: getExternalFeaturedImageAlt(), efiUrl: getExternalFeaturedImageUrl() || ( '' as Url ), followers: post.followers, highlights, isAutoShareEnabled: isAutoShareEnabled(), networkImageIds: getNetworkImageIds(), permalinkQueryArgs: getQueryArgs(), series: getSeries(), suggestedReferences: getSuggestedReferences(), }; if ( ! isEqual( values, prevValues ) ) { callback( values ); } prevValues = values; }, NC_EDIT_POST ); } export function renderMetaBox( id: string, Component: () => JSX.Element | null ): void { const el = document.getElementById( id ); if ( ! el ) { return; } const wrapper = el.querySelector( '.inside' ); if ( ! wrapper ) { return; } render( , wrapper ); } export function createSocialMessageUsingSelection( text: string, links: ReadonlyArray< Url > ): void { text = trim( text ); if ( isEmpty( text ) ) { return; } const twitter = searchTwitterHandleToMention( links ); if ( twitter ) { text = `${ text } /cc ${ twitter }`; } text += ' {permalink}'; const { getPost } = select( NC_EDIT_POST ); const { openNewSocialMessageEditor } = dispatch( NC_SOCIAL_EDITOR ); void openNewSocialMessageEditor( { text }, { context: 'post', post: getPost(), disabledProfileIds: getDisabledProfiles(), } ); } // ======= // HELPERS // ======= function addTinyMCEGlobal() { // eslint-disable-next-line ( window as any ).NelioContentTinyMCE = { createMessage: createSocialMessageUsingSelection, getLinks, isEmpty, }; } function searchTwitterHandleToMention( links: ReadonlyArray< Url > ) { if ( isEmpty( links ) ) { return false; } const { getSocialProfilesByNetwork } = select( NC_DATA ); if ( isEmpty( getSocialProfilesByNetwork( 'twitter' ) ) ) { return false; } const { getReferenceByUrl } = select( NC_EDIT_POST ); for ( const link of links ) { const data = getReferenceByUrl( link ); if ( data && data.twitter ) { return data.twitter; } } return false; } export async function loadPostFromWordPress( postId: PostId ): Promise< AwsPost > { return await apiFetch< AwsPost >( { path: `/nelio-content/v1/post/${ postId }?aws`, } ); } async function initPost( post: EditingPost ) { const { loadPostItems, setPost } = dispatch( NC_EDIT_POST ); await setPost( post ); void loadPostItems(); } function initDefaultFollowers( shouldAuthorBeFollower: boolean ) { const { includeAuthorInFollowers } = dispatch( NC_EDIT_POST ); void includeAuthorInFollowers( shouldAuthorBeFollower ); } function initAutoShareEndModes( autoShareEndModes: ReadonlyArray< AutoShareEndMode > ) { if ( isEmpty( autoShareEndModes ) ) { return; } const { setAutoShareEndModes } = dispatch( NC_EDIT_POST ); void setAutoShareEndModes( autoShareEndModes ); } function initExternalFeaturedImage( { url, alt, }: Args[ 'attributes' ][ 'externalFeatImage' ] ) { if ( ! url ) { return; } const { setExternalFeaturedImage } = dispatch( NC_EDIT_POST ); void setExternalFeaturedImage( url, alt ); } function initReferences( references: ReadonlyArray< EditorialReference > ) { const { receiveReferences, suggestReferences } = dispatch( NC_EDIT_POST ); void receiveReferences( references ); const suggestions = filter( references, { isSuggestion: true } ); if ( ! isEmpty( suggestions ) ) { void suggestReferences( map( suggestions, 'url' ) ); } } function customizeQualityAnalysis( quality: QualityAnalysisSettings ) { updateQualityAnalysisIntegration( quality ); updateYoastCheck( quality ); updateFeaturedImageCheck( quality ); updateAuthorCheck(); } function updateQualityAnalysisIntegration( quality: QualityAnalysisSettings ) { const { markQualityAnalysisAsFullyIntegrated } = dispatch( NC_EDIT_POST ); void markQualityAnalysisAsFullyIntegrated( !! quality.isFullyIntegrated ); } function updateYoastCheck( { isYoastIntegrated }: QualityAnalysisSettings ) { if ( isYoastIntegrated ) { return; } const { removeQualityCheckType } = dispatch( NC_EDIT_POST ); void removeQualityCheckType( 'nelio-content/yoast-content' ); void removeQualityCheckType( 'nelio-content/yoast-seo' ); } function updateFeaturedImageCheck( quality: QualityAnalysisSettings ) { const { supportsFeatImage, canImageBeAutoSet } = quality; if ( ! supportsFeatImage ) { const { removeQualityCheckType } = dispatch( NC_EDIT_POST ); void removeQualityCheckType( 'nelio-content/featured-image' ); return; } const { updateQualityCheckSettings } = dispatch( NC_EDIT_POST ); void updateQualityCheckSettings( 'nelio-content/featured-image', { canImageBeAutoSet, } ); } function updateAuthorCheck() { const { isMultiAuthor } = select( NC_DATA ); if ( isMultiAuthor() ) { return; } const { removeQualityCheckType } = dispatch( NC_EDIT_POST ); void removeQualityCheckType( 'nelio-content/author' ); } function keepRelatedItemsSynchronized() { let prevDate: Maybe< string >, prevStatus: string; // eslint-disable-next-line @typescript-eslint/no-unsafe-call subscribe( () => { const { getDate, getStatus } = select( NC_EDIT_POST ); const date = getDate(); const status = getStatus(); if ( date === prevDate && status === prevStatus ) { return; } prevDate = date; prevStatus = status; const { updateDatesInPostRelatedItems } = dispatch( NC_EDIT_POST ); void updateDatesInPostRelatedItems(); } ); } function setEditorType( isClassicEditor = false, isElementorEditor = false ) { const { setEditorToClassic, setEditorToElementor } = dispatch( NC_EDIT_POST ); void setEditorToClassic( isClassicEditor ); void setEditorToElementor( isElementorEditor ); }