Skip to content

Commit

Permalink
Add: Author nicename template creation ability (#42165)
Browse files Browse the repository at this point in the history
Co-authored-by: Miguel Fonseca <miguelcsf@gmail.com>
Co-authored-by: ntsekouras <ntsekouras@outlook.com>
  • Loading branch information
3 people authored Aug 2, 2022
1 parent 6135bc0 commit e3320c4
Show file tree
Hide file tree
Showing 2 changed files with 150 additions and 27 deletions.
43 changes: 24 additions & 19 deletions packages/edit-site/src/components/add-new-template/new-template.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import {
useDefaultTemplateTypes,
useTaxonomiesMenuItems,
usePostTypeMenuItems,
useAuthorMenuItem,
} from './utils';
import AddCustomGenericTemplateModal from './add-custom-generic-template-modal';
import { useHistory } from '../routes';
Expand Down Expand Up @@ -243,26 +244,30 @@ function useMissingTemplates(
useTaxonomiesMenuItems( onClickMenuItem );
const { defaultPostTypesMenuItems, postTypesMenuItems } =
usePostTypeMenuItems( onClickMenuItem );
[ ...defaultTaxonomiesMenuItems, ...defaultPostTypesMenuItems ].forEach(
( menuItem ) => {
if ( ! menuItem ) {
return;
}
const matchIndex = enhancedMissingDefaultTemplateTypes.findIndex(
( template ) => template.slug === menuItem.slug
);
// Some default template types might have been filtered above from
// `missingDefaultTemplates` because they only check for the general
// template. So here we either replace or append the item, augmented
// with the check if it has available specific item to create a
// template for.
if ( matchIndex > -1 ) {
enhancedMissingDefaultTemplateTypes[ matchIndex ] = menuItem;
} else {
enhancedMissingDefaultTemplateTypes.push( menuItem );
}

const authorMenuItem = useAuthorMenuItem( onClickMenuItem );
[
...defaultTaxonomiesMenuItems,
...defaultPostTypesMenuItems,
authorMenuItem,
].forEach( ( menuItem ) => {
if ( ! menuItem ) {
return;
}
);
const matchIndex = enhancedMissingDefaultTemplateTypes.findIndex(
( template ) => template.slug === menuItem.slug
);
// Some default template types might have been filtered above from
// `missingDefaultTemplates` because they only check for the general
// template. So here we either replace or append the item, augmented
// with the check if it has available specific item to create a
// template for.
if ( matchIndex > -1 ) {
enhancedMissingDefaultTemplateTypes[ matchIndex ] = menuItem;
} else {
enhancedMissingDefaultTemplateTypes.push( menuItem );
}
} );
// Update the sort order to match the DEFAULT_TEMPLATE_SLUGS order.
enhancedMissingDefaultTemplateTypes?.sort( ( template1, template2 ) => {
return (
Expand Down
134 changes: 126 additions & 8 deletions packages/edit-site/src/components/add-new-template/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { useSelect } from '@wordpress/data';
import { store as coreStore } from '@wordpress/core-data';
import { store as editorStore } from '@wordpress/editor';
import { decodeEntities } from '@wordpress/html-entities';
import { useMemo } from '@wordpress/element';
import { useMemo, useCallback } from '@wordpress/element';
import { __, sprintf } from '@wordpress/i18n';
import { blockMeta, post } from '@wordpress/icons';

Expand Down Expand Up @@ -408,6 +408,111 @@ export const useTaxonomiesMenuItems = ( onClickMenuItem ) => {
return taxonomiesMenuItems;
};

function useAuthorNeedsUniqueIndentifier() {
const authors = useSelect(
( select ) =>
select( coreStore ).getUsers( { who: 'authors', per_page: -1 } ),
[]
);
const authorsCountByName = useMemo( () => {
return ( authors || [] ).reduce( ( authorsCount, { name } ) => {
authorsCount[ name ] = ( authorsCount[ name ] || 0 ) + 1;
return authorsCount;
}, {} );
}, [ authors ] );
return useCallback(
( name ) => {
return authorsCountByName[ name ] > 1;
},
[ authorsCountByName ]
);
}

const USE_AUTHOR_MENU_ITEM_TEMPLATE_PREFIX = { user: 'author' };
const USE_AUTHOR_MENU_ITEM_QUERY_PARAMETERS = { user: { who: 'authors' } };
export function useAuthorMenuItem( onClickMenuItem ) {
const existingTemplates = useExistingTemplates();
const defaultTemplateTypes = useDefaultTemplateTypes();
const authorInfo = useEntitiesInfo(
'root',
USE_AUTHOR_MENU_ITEM_TEMPLATE_PREFIX,
USE_AUTHOR_MENU_ITEM_QUERY_PARAMETERS
);
const authorNeedsUniqueId = useAuthorNeedsUniqueIndentifier();
let authorMenuItem = defaultTemplateTypes?.find(
( { slug } ) => slug === 'author'
);
if ( ! authorMenuItem ) {
authorMenuItem = {
description: __(
'Displays latest posts written by a single author.'
),
slug: 'author',
title: 'Author',
};
}
const hasGeneralTemplate = !! existingTemplates?.find(
( { slug } ) => slug === 'author'
);
if ( authorInfo.user?.hasEntities ) {
authorMenuItem = { ...authorMenuItem };
authorMenuItem.onClick = ( template ) => {
onClickMenuItem( {
type: 'root',
slug: 'user',
config: {
queryArgs: ( { search } ) => {
return {
_fields: 'id,name,slug,link',
orderBy: search ? 'name' : 'registered_date',
exclude: authorInfo.user.existingEntitiesIds,
who: 'authors',
};
},
getSpecificTemplate: ( suggestion ) => {
const needsUniqueId = authorNeedsUniqueId(
suggestion.name
);
const title = needsUniqueId
? sprintf(
// translators: %1$s: Represents the name of an author e.g: "Jorge", %2$s: Represents the slug of an author e.g: "author-jorge-slug".
__( 'Author: %1$s (%2$s)' ),
suggestion.name,
suggestion.slug
)
: sprintf(
// translators: %s: Represents the name of an author e.g: "Jorge".
__( 'Author: %s' ),
suggestion.name
);
const description = sprintf(
// translators: %s: Represents the name of an author e.g: "Jorge".
__( 'Template for Author: %s' ),
suggestion.name
);
return {
title,
description,
slug: `author-${ suggestion.slug }`,
};
},
},
labels: {
singular_name: __( 'Author' ),
search_items: __( 'Search Authors' ),
not_found: __( 'No authors found.' ),
all_items: __( 'All Authors' ),
},
hasGeneralTemplate,
template,
} );
};
}
if ( ! hasGeneralTemplate || authorInfo.user?.hasEntities ) {
return authorMenuItem;
}
}

/**
* Helper hook that filters all the existing templates by the given
* object with the entity's slug as key and the template prefix as value.
Expand Down Expand Up @@ -456,11 +561,16 @@ const useExistingTemplateSlugs = ( templatePrefixes ) => {
* Helper hook that finds the existing records with an associated template,
* as they need to be excluded from the template suggestions.
*
* @param {string} entityName The entity's name.
* @param {Record<string,string>} templatePrefixes An object with the entity's slug as key and the template prefix as value.
* @param {string} entityName The entity's name.
* @param {Record<string,string>} templatePrefixes An object with the entity's slug as key and the template prefix as value.
* @param {Record<string,Object>} additionalQueryParameters An object with the entity's slug as key and additional query parameters as value.
* @return {Record<string,EntitiesInfo>} An object with the entity's slug as key and the existing records as value.
*/
const useTemplatesToExclude = ( entityName, templatePrefixes ) => {
const useTemplatesToExclude = (
entityName,
templatePrefixes,
additionalQueryParameters = {}
) => {
const slugsToExcludePerEntity =
useExistingTemplateSlugs( templatePrefixes );
const recordsToExcludePerEntity = useSelect(
Expand All @@ -473,6 +583,7 @@ const useTemplatesToExclude = ( entityName, templatePrefixes ) => {
_fields: 'id',
context: 'view',
slug: slugsWithTemplates,
...additionalQueryParameters[ slug ],
} );
if ( entitiesWithTemplates?.length ) {
accumulator[ slug ] = entitiesWithTemplates;
Expand All @@ -497,14 +608,20 @@ const useTemplatesToExclude = ( entityName, templatePrefixes ) => {
* First we need to find the existing records with an associated template,
* to query afterwards for any remaining record, by excluding them.
*
* @param {string} entityName The entity's name.
* @param {Record<string,string>} templatePrefixes An object with the entity's slug as key and the template prefix as value.
* @param {string} entityName The entity's name.
* @param {Record<string,string>} templatePrefixes An object with the entity's slug as key and the template prefix as value.
* @param {Record<string,Object>} additionalQueryParameters An object with the entity's slug as key and additional query parameters as value.
* @return {Record<string,EntitiesInfo>} An object with the entity's slug as key and the EntitiesInfo as value.
*/
const useEntitiesInfo = ( entityName, templatePrefixes ) => {
const useEntitiesInfo = (
entityName,
templatePrefixes,
additionalQueryParameters = {}
) => {
const recordsToExcludePerEntity = useTemplatesToExclude(
entityName,
templatePrefixes
templatePrefixes,
additionalQueryParameters
);
const entitiesInfo = useSelect(
( select ) => {
Expand All @@ -523,6 +640,7 @@ const useEntitiesInfo = ( entityName, templatePrefixes ) => {
_fields: 'id',
context: 'view',
exclude: existingEntitiesIds,
...additionalQueryParameters[ slug ],
}
)?.length,
existingEntitiesIds,
Expand Down

0 comments on commit e3320c4

Please sign in to comment.