import { __ } from 'ct-i18n' import { ToggleControl } from '@wordpress/components' import { FormTokenField } from '@wordpress/components' import { useDebounce } from '@wordpress/compose' import { useState, createElement, useRef, useEffect } from '@wordpress/element' import { useSelect } from '@wordpress/data' import { store as coreStore } from '@wordpress/core-data' const BASE_QUERY = { order: 'asc', _fields: 'id,title', context: 'view', } const EMPTY_ARRAY = [] const getPostIdByPostValue = (posts, postValue) => { const postId = postValue?.id || posts?.find((post) => post.title.rendered === postValue)?.id if (postId) { return postId } const postValueLower = postValue.toLocaleLowerCase() return posts?.find( (post) => post.title.rendered.toLocaleLowerCase() === postValueLower )?.id } function PostItem({ label, post_type, postIds, onChange, hasLabel = false }) { const [search, setSearch] = useState('') const [value, setValue] = useState(EMPTY_ARRAY) const [suggestions, setSuggestions] = useState(EMPTY_ARRAY) const debouncedSearch = useDebounce(setSearch, 250) const { searchResults, searchHasResolved } = useSelect( (select) => { if (!search) { return { searchResults: EMPTY_ARRAY, searchHasResolved: true } } const { getEntityRecords, hasFinishedResolution } = select(coreStore) const selectorArgs = [ 'postType', post_type, { ...BASE_QUERY, search, orderby: 'title', exclude: postIds, per_page: 20, }, ] return { searchResults: getEntityRecords(...selectorArgs), searchHasResolved: hasFinishedResolution( 'getEntityRecords', selectorArgs ), } }, [search, postIds, post_type] ) const existingPosts = useSelect( (select) => { if (!postIds?.length) return EMPTY_ARRAY const { getEntityRecords } = select(coreStore) return getEntityRecords('postType', post_type, { ...BASE_QUERY, include: postIds, per_page: postIds.length, }) }, [postIds] ) useEffect(() => { if (!postIds?.length) { setValue(EMPTY_ARRAY) } if (!existingPosts?.length) return // Returns only the existing entity ids. This prevents the component // from crashing in the editor, when non existing ids are provided. const sanitizedValue = postIds.reduce((accumulator, id) => { const entity = existingPosts.find((post) => post.id === id) if (entity) { accumulator.push({ id, value: entity.title.rendered, }) } return accumulator }, []) setValue(sanitizedValue) }, [postIds, existingPosts]) // Update suggestions only when the query has resolved. useEffect(() => { if (!searchHasResolved) return setSuggestions(searchResults.map((result) => result.title.rendered)) }, [searchResults, searchHasResolved]) const onPostsChange = (newPostValues) => { const newPostIds = new Set() for (const postValue of newPostValues) { const postId = getPostIdByPostValue(searchResults, postValue) if (postId) { newPostIds.add(postId) } } setSuggestions(EMPTY_ARRAY) onChange(Array.from(newPostIds)) } return ( ) } const ManagePosts = ({ attributes, setAttributes, fieldId = 'exclude_post_ids', label, excludeLabel, previewedPostMatchesType, }) => { return ( <> { setAttributes({ [fieldId]: { ...attributes[fieldId], ids: newPostIds, }, }) }} label={label} /> {previewedPostMatchesType && ( setAttributes({ [fieldId]: { ...attributes[fieldId], current_post: !attributes[fieldId].current_post, }, }) } /> )} ) } export default ManagePosts