From 3881e9d90f6a1e658b5b1b4c0419a8a9aa124b5d Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Fri, 27 Sep 2024 15:36:25 +0530 Subject: [PATCH 1/5] Use `HierarchicalTermSelector` from @wordpress/editor as we have crossed 6.0 as WP minimum --- assets/js/edit.js | 8 +- .../hierarchical-term-selector.js | 475 ------------------ assets/js/term-selector/terms.js | 72 --- 3 files changed, 1 insertion(+), 554 deletions(-) delete mode 100644 assets/js/term-selector/hierarchical-term-selector.js delete mode 100644 assets/js/term-selector/terms.js diff --git a/assets/js/edit.js b/assets/js/edit.js index 05d49eac..450242f1 100644 --- a/assets/js/edit.js +++ b/assets/js/edit.js @@ -26,13 +26,7 @@ import { Button } from '@wordpress/components'; import { useState, useEffect } from '@wordpress/element'; import { dispatch, useSelect, useDispatch } from '@wordpress/data'; import { createBlock } from '@wordpress/blocks'; - -/* - * Import hierarchical term selector. - * - * @TODO Import from `@wordpress/editor` once minimum WP version is 6.0. - */ -import HierarchicalTermSelector from './term-selector/hierarchical-term-selector'; +import { PostTaxonomiesHierarchicalTermSelector as HierarchicalTermSelector } from '@wordpress/editor'; function useFeaturedImage() { const featuredImageId = useSelect((select) => select('core/editor').getEditedPostAttribute('featured_media'), []); diff --git a/assets/js/term-selector/hierarchical-term-selector.js b/assets/js/term-selector/hierarchical-term-selector.js deleted file mode 100644 index 8e0525c8..00000000 --- a/assets/js/term-selector/hierarchical-term-selector.js +++ /dev/null @@ -1,475 +0,0 @@ -/** - * External dependencies - */ -import { find, get, some, unescape as unescapeString, without } from 'lodash'; - -/** - * WordPress dependencies - */ -import { __, _n, _x, sprintf } from '@wordpress/i18n'; -import { useMemo, useState } from '@wordpress/element'; -import { - Button, - CheckboxControl, - TextControl, - TreeSelect, - withFilters, -} from '@wordpress/components'; -import { useDispatch, useSelect } from '@wordpress/data'; -import { useDebounce } from '@wordpress/compose'; -import { store as coreStore } from '@wordpress/core-data'; -import { speak } from '@wordpress/a11y'; - -/** - * Internal dependencies - */ -import { buildTermsTree } from './terms'; - -/** - * Module Constants - */ -const DEFAULT_QUERY = { - per_page: -1, - orderby: 'name', - order: 'asc', - _fields: 'id,name,parent', - context: 'view', -}; - -const MIN_TERMS_COUNT_FOR_FILTER = 8; - -const EMPTY_ARRAY = []; - -/** - * Sort Terms by Selected. - * - * @param {Object[]} termsTree Array of terms in tree format. - * @param {number[]} terms Selected terms. - * - * @return {Object[]} Sorted array of terms. - */ -export function sortBySelected( termsTree, terms ) { - const treeHasSelection = ( termTree ) => { - if ( terms.indexOf( termTree.id ) !== -1 ) { - return true; - } - if ( undefined === termTree.children ) { - return false; - } - return ( - termTree.children - .map( treeHasSelection ) - .filter( ( child ) => child ).length > 0 - ); - }; - const termOrChildIsSelected = ( termA, termB ) => { - const termASelected = treeHasSelection( termA ); - const termBSelected = treeHasSelection( termB ); - - if ( termASelected === termBSelected ) { - return 0; - } - - if ( termASelected && ! termBSelected ) { - return -1; - } - - if ( ! termASelected && termBSelected ) { - return 1; - } - - return 0; - }; - const newTermTree = [ ...termsTree ]; - newTermTree.sort( termOrChildIsSelected ); - return newTermTree; -} - -/** - * Find term by parent id or name. - * - * @param {Object[]} terms Array of Terms. - * @param {number|string} parent id. - * @param {string} name Term name. - * @return {Object} Term object. - */ -export function findTerm( terms, parent, name ) { - return find( terms, ( term ) => { - return ( - ( ( ! term.parent && ! parent ) || - parseInt( term.parent ) === parseInt( parent ) ) && - term.name.toLowerCase() === name.toLowerCase() - ); - } ); -} - -/** - * Get filter matcher function. - * - * @param {string} filterValue Filter value. - * @return {(function(Object): (Object|boolean))} Matcher function. - */ -export function getFilterMatcher( filterValue ) { - const matchTermsForFilter = ( originalTerm ) => { - if ( '' === filterValue ) { - return originalTerm; - } - - // Shallow clone, because we'll be filtering the term's children and - // don't want to modify the original term. - const term = { ...originalTerm }; - - // Map and filter the children, recursive so we deal with grandchildren - // and any deeper levels. - if ( term.children.length > 0 ) { - term.children = term.children - .map( matchTermsForFilter ) - .filter( ( child ) => child ); - } - - // If the term's name contains the filterValue, or it has children - // (i.e. some child matched at some point in the tree) then return it. - if ( - -1 !== - term.name.toLowerCase().indexOf( filterValue.toLowerCase() ) || - term.children.length > 0 - ) { - return term; - } - - // Otherwise, return false. After mapping, the list of terms will need - // to have false values filtered out. - return false; - }; - return matchTermsForFilter; -} - -/** - * Hierarchical term selector. - * - * @param {Object} props Component props. - * @param {string} props.slug Taxonomy slug. - * @return {WPElement} Hierarchical term selector component. - */ -export function HierarchicalTermSelector( { slug } ) { - const [ adding, setAdding ] = useState( false ); - const [ formName, setFormName ] = useState( '' ); - /** - * @type {[number|'', Function]} - */ - const [ formParent, setFormParent ] = useState( '' ); - const [ showForm, setShowForm ] = useState( false ); - const [ filterValue, setFilterValue ] = useState( '' ); - const [ filteredTermsTree, setFilteredTermsTree ] = useState( [] ); - const debouncedSpeak = useDebounce( speak, 500 ); - - const { - hasCreateAction, - hasAssignAction, - terms, - loading, - availableTerms, - taxonomy, - } = useSelect( - ( select ) => { - const { getCurrentPost, getEditedPostAttribute } = - select( 'core/editor' ); - const { getTaxonomy, getEntityRecords, isResolving } = - select( coreStore ); - const _taxonomy = getTaxonomy( slug ); - - return { - hasCreateAction: _taxonomy - ? get( - getCurrentPost(), - [ - '_links', - 'wp:action-create-' + _taxonomy.rest_base, - ], - false - ) - : false, - hasAssignAction: _taxonomy - ? get( - getCurrentPost(), - [ - '_links', - 'wp:action-assign-' + _taxonomy.rest_base, - ], - false - ) - : false, - terms: _taxonomy - ? getEditedPostAttribute( _taxonomy.rest_base ) - : EMPTY_ARRAY, - loading: isResolving( 'getEntityRecords', [ - 'taxonomy', - slug, - DEFAULT_QUERY, - ] ), - availableTerms: - getEntityRecords( 'taxonomy', slug, DEFAULT_QUERY ) || - EMPTY_ARRAY, - taxonomy: _taxonomy, - }; - }, - [ slug ] - ); - - const { editPost } = useDispatch( 'core/editor' ); - const { saveEntityRecord } = useDispatch( coreStore ); - - const availableTermsTree = useMemo( - () => sortBySelected( buildTermsTree( availableTerms ), terms ), - // Remove `terms` from the dependency list to avoid reordering every time - // checking or unchecking a term. - [ availableTerms ] - ); - - if ( ! hasAssignAction ) { - return null; - } - - /** - * Append new term. - * - * @param {Object} term Term object. - * @return {Promise} A promise that resolves to save term object. - */ - const addTerm = ( term ) => { - return saveEntityRecord( 'taxonomy', slug, term ); - }; - - /** - * Update terms for post. - * - * @param {number[]} termIds Term ids. - */ - const onUpdateTerms = ( termIds ) => { - editPost( { [ taxonomy.rest_base ]: termIds } ); - }; - - /** - * Handler for checking term. - * - * @param {number} termId - */ - const onChange = ( termId ) => { - const hasTerm = terms.includes( termId ); - const newTerms = hasTerm - ? without( terms, termId ) - : [ ...terms, termId ]; - onUpdateTerms( newTerms ); - }; - - const onChangeFormName = ( value ) => { - setFormName( value ); - }; - - /** - * Handler for changing form parent. - * - * @param {number|''} parentId Parent post id. - */ - const onChangeFormParent = ( parentId ) => { - setFormParent( parentId ); - }; - - const onToggleForm = () => { - setShowForm( ! showForm ); - }; - - const onAddTerm = async ( event ) => { - event.preventDefault(); - if ( formName === '' || adding ) { - return; - } - - // Check if the term we are adding already exists. - const existingTerm = findTerm( availableTerms, formParent, formName ); - if ( existingTerm ) { - // If the term we are adding exists but is not selected select it. - if ( ! some( terms, ( term ) => term === existingTerm.id ) ) { - onUpdateTerms( [ ...terms, existingTerm.id ] ); - } - - setFormName( '' ); - setFormParent( '' ); - - return; - } - setAdding( true ); - - const newTerm = await addTerm( { - name: formName, - parent: formParent ? formParent : undefined, - } ); - - const termAddedMessage = sprintf( - /* translators: %s: taxonomy name */ - _x( '%s added', 'term' ), - get( - taxonomy, - [ 'labels', 'singular_name' ], - slug === 'category' ? __( 'Category' ) : __( 'Term' ) - ) - ); - speak( termAddedMessage, 'assertive' ); - setAdding( false ); - setFormName( '' ); - setFormParent( '' ); - onUpdateTerms( [ ...terms, newTerm.id ] ); - }; - - const setFilter = ( value ) => { - const newFilteredTermsTree = availableTermsTree - .map( getFilterMatcher( value ) ) - .filter( ( term ) => term ); - const getResultCount = ( termsTree ) => { - let count = 0; - for ( let i = 0; i < termsTree.length; i++ ) { - count++; - if ( undefined !== termsTree[ i ].children ) { - count += getResultCount( termsTree[ i ].children ); - } - } - return count; - }; - - setFilterValue( value ); - setFilteredTermsTree( newFilteredTermsTree ); - - const resultCount = getResultCount( newFilteredTermsTree ); - const resultsFoundMessage = sprintf( - /* translators: %d: number of results */ - _n( '%d result found.', '%d results found.', resultCount ), - resultCount - ); - - debouncedSpeak( resultsFoundMessage, 'assertive' ); - }; - - const renderTerms = ( renderedTerms ) => { - return renderedTerms.map( ( term ) => { - return ( -
- { - const termId = parseInt( term.id, 10 ); - onChange( termId ); - } } - label={ unescapeString( term.name ) } - /> - { !! term.children.length && ( -
- { renderTerms( term.children ) } -
- ) } -
- ); - } ); - }; - - const labelWithFallback = ( - labelProperty, - fallbackIsCategory, - fallbackIsNotCategory - ) => - get( - taxonomy, - [ 'labels', labelProperty ], - slug === 'category' ? fallbackIsCategory : fallbackIsNotCategory - ); - const newTermButtonLabel = labelWithFallback( - 'add_new_item', - __( 'Add new category' ), - __( 'Add new term' ) - ); - const newTermLabel = labelWithFallback( - 'new_item_name', - __( 'Add new category' ), - __( 'Add new term' ) - ); - const parentSelectLabel = labelWithFallback( - 'parent_item', - __( 'Parent Category' ), - __( 'Parent Term' ) - ); - const noParentOption = `— ${ parentSelectLabel } —`; - const newTermSubmitLabel = newTermButtonLabel; - const filterLabel = get( - taxonomy, - [ 'labels', 'search_items' ], - __( 'Search Terms' ) - ); - const groupLabel = get( taxonomy, [ 'name' ], __( 'Terms' ) ); - const showFilter = availableTerms.length >= MIN_TERMS_COUNT_FOR_FILTER; - - return ( - <> - { showFilter && ( - - ) } -
- { renderTerms( - '' !== filterValue ? filteredTermsTree : availableTermsTree - ) } -
- { ! loading && hasCreateAction && ( - - ) } - { showForm && ( -
- - { !! availableTerms.length && ( - - ) } - - - ) } - - ); -} - -export default withFilters( 'editor.PostTaxonomyType' )( - HierarchicalTermSelector -); diff --git a/assets/js/term-selector/terms.js b/assets/js/term-selector/terms.js deleted file mode 100644 index 8aeb079d..00000000 --- a/assets/js/term-selector/terms.js +++ /dev/null @@ -1,72 +0,0 @@ -/** - * External dependencies - */ -import { groupBy, map, unescape as lodashUnescapeString } from 'lodash'; - -/** - * Returns terms in a tree form. - * - * @param {Array} flatTerms Array of terms in flat format. - * - * @return {Array} Array of terms in tree format. - */ -export function buildTermsTree( flatTerms ) { - const flatTermsWithParentAndChildren = flatTerms.map( ( term ) => { - return { - children: [], - parent: null, - ...term, - }; - } ); - - const termsByParent = groupBy( flatTermsWithParentAndChildren, 'parent' ); - if ( termsByParent.null && termsByParent.null.length ) { - return flatTermsWithParentAndChildren; - } - const fillWithChildren = ( terms ) => { - return terms.map( ( term ) => { - const children = termsByParent[ term.id ]; - return { - ...term, - children: - children && children.length - ? fillWithChildren( children ) - : [], - }; - } ); - }; - - return fillWithChildren( termsByParent[ '0' ] || [] ); -} - -// Lodash unescape function handles ' but not ' which may be return in some API requests. -export const unescapeString = ( arg ) => { - return lodashUnescapeString( arg.replace( ''', "'" ) ); -}; - -/** - * Returns a term object with name unescaped. - * The unescape of the name property is done using lodash unescape function. - * - * @param {Object} term The term object to unescape. - * - * @return {Object} Term object with name property unescaped. - */ -export const unescapeTerm = ( term ) => { - return { - ...term, - name: unescapeString( term.name ), - }; -}; - -/** - * Returns an array of term objects with names unescaped. - * The unescape of each term is performed using the unescapeTerm function. - * - * @param {Object[]} terms Array of term objects to unescape. - * - * @return {Object[]} Array of term objects unescaped. - */ -export const unescapeTerms = ( terms ) => { - return map( terms, unescapeTerm ); -}; From ba197274261316c28beae0634570a1db11d7f4eb Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Fri, 27 Sep 2024 15:45:53 +0530 Subject: [PATCH 2/5] Fix podcast term unselect bug. --- assets/js/edit.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/assets/js/edit.js b/assets/js/edit.js index 450242f1..6fdda4c9 100644 --- a/assets/js/edit.js +++ b/assets/js/edit.js @@ -82,7 +82,9 @@ function Edit( props ) { const [ src, setSrc ] = useState( props.attributes.src ); - useEffect( () => () => wp.data.dispatch('core/editor').editPost({ podcasting_podcasts: [] }) ); + useEffect( () => { + return () => wp.data.dispatch('core/editor').editPost({ podcasting_podcasts: [] }); + }, [] ); const postTitle = useSelect( ( select ) => select( 'core/editor' ).getEditedPostAttribute( 'title' ) ); const onSelectAttachment = (attachment) => { @@ -193,6 +195,7 @@ function Edit( props ) {
From 2ab382a9f1591e3d069dd4d019c3472516ef0b79 Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Fri, 27 Sep 2024 15:46:07 +0530 Subject: [PATCH 3/5] Some design changes. --- assets/css/podcasting-editor-screen.css | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/assets/css/podcasting-editor-screen.css b/assets/css/podcasting-editor-screen.css index 2566d930..91080810 100644 --- a/assets/css/podcasting-editor-screen.css +++ b/assets/css/podcasting-editor-screen.css @@ -11,3 +11,11 @@ .cover-art-container button { margin: 10px 0; } + +.simple-podcast-settings #hierar-podcasting_podcasts .components-base-control { + margin-bottom: 0px; +} + +.simple-podcast-settings #hierar-podcasting_podcasts { + margin-bottom: 16px; +} From bce6507c9f37f639efa4dfe5c9c2b93110d9d4c5 Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Fri, 8 Nov 2024 20:34:43 +0530 Subject: [PATCH 4/5] Remove podcast selector from block settings. --- assets/css/podcasting-editor-screen.css | 8 -------- assets/js/edit.js | 6 ------ 2 files changed, 14 deletions(-) diff --git a/assets/css/podcasting-editor-screen.css b/assets/css/podcasting-editor-screen.css index 91080810..2566d930 100644 --- a/assets/css/podcasting-editor-screen.css +++ b/assets/css/podcasting-editor-screen.css @@ -11,11 +11,3 @@ .cover-art-container button { margin: 10px 0; } - -.simple-podcast-settings #hierar-podcasting_podcasts .components-base-control { - margin-bottom: 0px; -} - -.simple-podcast-settings #hierar-podcasting_podcasts { - margin-bottom: 16px; -} diff --git a/assets/js/edit.js b/assets/js/edit.js index 6fdda4c9..7c2a83b0 100644 --- a/assets/js/edit.js +++ b/assets/js/edit.js @@ -26,7 +26,6 @@ import { Button } from '@wordpress/components'; import { useState, useEffect } from '@wordpress/element'; import { dispatch, useSelect, useDispatch } from '@wordpress/data'; import { createBlock } from '@wordpress/blocks'; -import { PostTaxonomiesHierarchicalTermSelector as HierarchicalTermSelector } from '@wordpress/editor'; function useFeaturedImage() { const featuredImageId = useSelect((select) => select('core/editor').getEditedPostAttribute('featured_media'), []); @@ -197,11 +196,6 @@ function Edit( props ) { title={__('Podcast Settings', 'simple-podcasting')} className="simple-podcast-settings" > - -
- -
-
Date: Fri, 8 Nov 2024 23:21:36 +0530 Subject: [PATCH 5/5] - Fixed issue with podcast show selection and display current selected show in editor block preview --- assets/js/create-podcast-show.js | 26 +++++++------------------- assets/js/edit.js | 25 +++++++++++-------------- 2 files changed, 18 insertions(+), 33 deletions(-) diff --git a/assets/js/create-podcast-show.js b/assets/js/create-podcast-show.js index 6c7fdfd8..018898c6 100644 --- a/assets/js/create-podcast-show.js +++ b/assets/js/create-podcast-show.js @@ -263,18 +263,9 @@ const CreatePodcastShowPlugin = () => { const [ isModalOpen, setIsModalOpen ] = useState( false ); const openModal = () => setIsModalOpen( true ); const closeModal = () => setIsModalOpen( false ); - const initAttachedPodcastIds = attachedPodcasts.map( ( item ) => item.id ); - const [attachedPodcastIds, setAttachedPodcastIds] = useState([]); - - /* - * This is a workaround for WP 5.7 to prevent infinite loop - * when setting state. - * - * @todo remove this when the min supported WP version is bumped - * to 6.1 - */ - const [isAttached, setisAttached] = useState( true ); - + const attachedPodcastIds = useSelect( ( select ) => { + return select( 'core/editor' ).getEditedPostAttribute( 'podcasting_podcasts' ); + } ); /** * Attaches the podcast term to the current post if selected. @@ -283,12 +274,12 @@ const CreatePodcastShowPlugin = () => { * @param {Integer} podcastId The podcast term ID. */ function attachPodcastToPost( isChecked, podcastId ) { - let updatedAttachedPodcastIds = [ ...initAttachedPodcastIds, ...attachedPodcastIds, podcastId ]; + let updatedAttachedPodcastIds = [ ...attachedPodcastIds, podcastId ]; if ( isChecked ) { - updatedAttachedPodcastIds = [ ...initAttachedPodcastIds, ...attachedPodcastIds, podcastId ]; + updatedAttachedPodcastIds = [ ...attachedPodcastIds, podcastId ]; } else { - updatedAttachedPodcastIds = [...initAttachedPodcastIds,...attachedPodcastIds].filter( ( currentPodcastId ) => currentPodcastId !== podcastId ); + updatedAttachedPodcastIds = attachedPodcastIds.filter( ( currentPodcastId ) => currentPodcastId !== podcastId ); } dispatch( coreDataStore ).editEntityRecord( @@ -299,9 +290,6 @@ const CreatePodcastShowPlugin = () => { podcasting_podcasts: updatedAttachedPodcastIds, } ) - - setAttachedPodcastIds( updatedAttachedPodcastIds ); - setisAttached( false ); } return ( @@ -318,7 +306,7 @@ const CreatePodcastShowPlugin = () => { key={ index } label={ item.name } onChange={ ( isChecked ) => attachPodcastToPost( isChecked, item.id ) } - checked={ isAttached ? initAttachedPodcastIds.includes( item.id ) : attachedPodcastIds.includes( item.id ) } + checked={ attachedPodcastIds.includes( item.id ) } /> ) } ) diff --git a/assets/js/edit.js b/assets/js/edit.js index 7c2a83b0..b7e4e0d9 100644 --- a/assets/js/edit.js +++ b/assets/js/edit.js @@ -81,9 +81,6 @@ function Edit( props ) { const [ src, setSrc ] = useState( props.attributes.src ); - useEffect( () => { - return () => wp.data.dispatch('core/editor').editPost({ podcasting_podcasts: [] }); - }, [] ); const postTitle = useSelect( ( select ) => select( 'core/editor' ).getEditedPostAttribute( 'title' ) ); const onSelectAttachment = (attachment) => { @@ -171,18 +168,18 @@ function Edit( props ) { ); - const { getCurrentPost } = select('core/editor'); - const postDetails = getCurrentPost(); - - const showId = postDetails ? postDetails.podcasting_podcasts[0] : null; - - const show = select('core').getEntityRecords('taxonomy', 'podcasting_podcasts', { - per_page: 1, - term_id: showId, - }); + const showId = useSelect( ( __select ) => { + const attachedPodcastIds = __select( 'core/editor' ).getEditedPostAttribute( 'podcasting_podcasts' ); + return attachedPodcastIds ? attachedPodcastIds[0] : null; + } ); + const show = useSelect( ( __select ) => { + return __select('core').getEntityRecords('taxonomy', 'podcasting_podcasts', { + include: [ showId ], + }); + } ); - const showName = show ? show[0].name : null; - const showImage = show ? show[0].meta.podcasting_image_url : null; + const showName = show && show[0] ? show[0]?.name : null; + const showImage = show && show[0] ? show[0]?.meta?.podcasting_image_url : null; const onUpdateImage = (image) => { setFeaturedImage(image.id);