Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

use WP_Block_Editor_Context;
use function WordPressdotorg\MU_Plugins\Global_Header_Footer\{ is_rosetta_site, get_rosetta_name };
use function WordPressdotorg\Pattern_Directory\Pattern_Post_Type\get_selectable_category_slugs;
use const WordPressdotorg\Pattern_Directory\Pattern_Post_Type\POST_TYPE;

const AUTOSAVE_INTERVAL = 30;
Expand Down Expand Up @@ -126,7 +127,8 @@ function pattern_creator_init() {
rawurlencode(
wp_json_encode(
array(
'siteUrl' => esc_url( home_url() ),
'siteUrl' => esc_url( home_url() ),
'categorySlugs' => get_selectable_category_slugs(),
)
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { find, get, unescape as unescapeString } from 'lodash';
* WordPress dependencies
*/
import { __, _n, sprintf } from '@wordpress/i18n';
import { CheckboxControl, TextControl } from '@wordpress/components';
import { CheckboxControl, Flex, TextControl } from '@wordpress/components';
import { speak } from '@wordpress/a11y';
import { store as coreStore } from '@wordpress/core-data';
import { store as editorStore } from '@wordpress/editor';
Expand All @@ -19,18 +19,11 @@ import { useSelect } from '@wordpress/data';
* Internal dependencies
*/
import { CATEGORY_SLUG } from '../../store';
import usePatternCategories from '../../hooks/use-pattern-categories';

/**
* Module Constants
*/
const DEFAULT_QUERY = {
per_page: -1,
orderby: 'name',
order: 'asc',
_fields: 'id,name,parent,slug',
context: 'view',
};

const MIN_TERMS_COUNT_FOR_FILTER = 8;

const EMPTY_ARRAY = [];
Expand Down Expand Up @@ -113,20 +106,14 @@ function PatternCategoriesControl( { selectedTerms = EMPTY_ARRAY, setTerms } ) {
const [ filteredTermsTree, setFilteredTermsTree ] = useState( [] );
const debouncedSpeak = useDebounce( speak, 500 );

const { hasAssignAction, availableTerms } = useSelect( ( select ) => {
const availableTerms = usePatternCategories();
const hasAssignAction = useSelect( ( select ) => {
const { getCurrentPost } = select( editorStore );
const { getTaxonomy, getEntityRecords } = select( coreStore );
const _taxonomy = getTaxonomy( CATEGORY_SLUG );

const terms = getEntityRecords( 'taxonomy', CATEGORY_SLUG, DEFAULT_QUERY ) || EMPTY_ARRAY;
const _taxonomy = select( coreStore ).getTaxonomy( CATEGORY_SLUG );

return {
hasAssignAction: _taxonomy
? get( getCurrentPost(), [ '_links', 'wp:action-assign-' + _taxonomy.rest_base ], false )
: false,
// Filter out the "Featured" category.
availableTerms: terms.filter( ( { slug } ) => 'featured' !== slug ),
};
return _taxonomy
? get( getCurrentPost(), [ '_links', 'wp:action-assign-' + _taxonomy.rest_base ], false )
: false;
}, [] );

const availableTermsTree = useMemo(
Expand Down Expand Up @@ -182,7 +169,7 @@ function PatternCategoriesControl( { selectedTerms = EMPTY_ARRAY, setTerms } ) {
const showFilter = availableTerms.length >= MIN_TERMS_COUNT_FOR_FILTER;

return (
<>
<Flex direction="column" gap="4">
{ showFilter && (
<TextControl
className="editor-post-taxonomies__hierarchical-terms-filter"
Expand Down Expand Up @@ -212,7 +199,7 @@ function PatternCategoriesControl( { selectedTerms = EMPTY_ARRAY, setTerms } ) {
);
} ) }
</div>
</>
</Flex>
);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,24 @@
/* global wporgBlockPattern, wporgLocale */
/**
* External dependencies
*/
import { unescape as unescapeString } from 'lodash';

/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import { addQueryArgs } from '@wordpress/url';
import apiFetch from '@wordpress/api-fetch';
import { Button, CheckboxControl, Modal, TextControl, TextareaControl } from '@wordpress/components';
import { createInterpolateElement, useEffect, useRef, useState } from '@wordpress/element';
import { store as editorStore } from '@wordpress/editor';
import { store as noticesStore } from '@wordpress/notices';
import { useDispatch, useSelect } from '@wordpress/data';

/**
* Internal dependencies
*/
import usePatternCategories from '../../hooks/use-pattern-categories';

const ForwardButton = ( { children, disabled, onClick } ) => (
<Button className="pattern-modal-publish__button" isPrimary disabled={ disabled } onClick={ onClick }>
{ children }
Expand Down Expand Up @@ -49,27 +57,12 @@ export default function SubmissionModal( { onClose, onSubmit, status } ) {
const [ title, setTitle ] = useState( postTitle );
const [ description, setDescription ] = useState( meta.wpop_description );
const [ selectedCategories, setSelectedCategories ] = useState( postCategories );
const [ categories, setCategories ] = useState( [] );
const categories = usePatternCategories();
// Retired categories stay on older patterns but aren't listed, so they can't satisfy the requirement.
const hasSelectableCategory = categories.some( ( term ) => selectedCategories.includes( term.id ) );
const [ currentPage, setCurrentPage ] = useState( 0 );
const container = useRef();

useEffect( () => {
apiFetch( {
path: addQueryArgs( '/wp/v2/pattern-categories' ),
} ).then( ( res ) => {
// Filter out the "Featured" category.
const terms = res.filter( ( { slug } ) => 'featured' !== slug );
setCategories(
terms.map( ( i ) => {
return {
value: i.id,
label: i.name,
};
} )
);
} );
}, [] );

useEffect( () => {
editPost( {
meta: {
Expand Down Expand Up @@ -204,18 +197,18 @@ export default function SubmissionModal( { onClose, onSubmit, status } ) {
className="submission-modal__checkbox-list"
aria-label={ __( 'Pattern categories', 'wporg-patterns' ) }
>
{ categories.map( ( i ) => (
<li key={ i.value }>
{ categories.map( ( term ) => (
<li key={ term.id }>
<CheckboxControl
label={ i.label }
value={ i.value }
checked={ selectedCategories.includes( i.value ) }
label={ unescapeString( term.name ) }
value={ term.id }
checked={ selectedCategories.includes( term.id ) }
onChange={ ( checked ) => {
if ( checked ) {
setSelectedCategories( [ ...selectedCategories, i.value ] );
setSelectedCategories( [ ...selectedCategories, term.id ] );
} else {
setSelectedCategories(
selectedCategories.filter( ( cat ) => cat !== i.value )
selectedCategories.filter( ( cat ) => cat !== term.id )
);
}
} }
Expand All @@ -229,7 +222,7 @@ export default function SubmissionModal( { onClose, onSubmit, status } ) {
<>
<Button onClick={ goBack }>{ __( 'Previous', 'wporg-patterns' ) }</Button>
<ForwardButton
disabled={ ! selectedCategories.length }
disabled={ ! hasSelectableCategory }
onClick={ () => {
onSubmit();
goForward();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ $modal-height: 375px;
display: grid;
grid-template-columns: 30% 70%;
margin: 0 auto;
height: $modal-height;
min-height: $modal-height;
color: #32373c;

fieldset {
Expand Down Expand Up @@ -101,13 +101,15 @@ h3.submission-modal__title-sidebar {
}

.submission-modal__checkbox-list {
display: grid;
grid-template-columns: 50% 50%;
grid-gap: $grid-unit-05;
// Use columns rather than a grid so the categories read alphabetically down each column.
columns: 2;
column-gap: $grid-unit-05;
margin: 0;

li {
list-style: none;
margin-bottom: $grid-unit-05;
break-inside: avoid;
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/**
* Internal dependencies
*/
import { filterSelectableCategories } from '../use-pattern-categories';

describe( 'filterSelectableCategories', () => {
const terms = [
{ id: 47, name: 'About', slug: 'about' },
{ id: 3, name: 'Columns', slug: 'columns' },
{ id: 26, name: 'Featured', slug: 'featured' },
{ id: 38, name: 'Posts', slug: 'query' },
{ id: 6, name: 'Text', slug: 'text' },
];

it( 'keeps only terms with an allowed slug, in their original order', () => {
expect( filterSelectableCategories( terms, [ 'text', 'query', 'about' ] ) ).toEqual( [
{ id: 47, name: 'About', slug: 'about' },
{ id: 38, name: 'Posts', slug: 'query' },
{ id: 6, name: 'Text', slug: 'text' },
] );
} );

it( 'ignores allowed slugs that have no matching term', () => {
expect( filterSelectableCategories( terms, [ 'video', 'about' ] ) ).toEqual( [
{ id: 47, name: 'About', slug: 'about' },
] );
} );

it( 'returns nothing when no slugs are allowed', () => {
expect( filterSelectableCategories( terms, [] ) ).toEqual( [] );
} );
} );
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/* global wporgBlockPattern */
/**
* WordPress dependencies
*/
import { store as coreStore } from '@wordpress/core-data';
import { useSelect } from '@wordpress/data';
import { useMemo } from '@wordpress/element';

/**
* Internal dependencies
*/
import { CATEGORY_SLUG } from '../store';

const QUERY = {
per_page: -1,
orderby: 'name',
order: 'asc',
_fields: 'id,name,parent,slug',
context: 'view',
};

const EMPTY_ARRAY = [];

/**
* Filter a list of category terms down to the ones authors can select.
*
* @param {Object[]} terms Category terms.
* @param {string[]} allowedSlugs Slugs of the selectable categories.
*
* @return {Object[]} Selectable category terms.
*/
export function filterSelectableCategories( terms, allowedSlugs ) {
return terms.filter( ( { slug } ) => allowedSlugs.includes( slug ) );
}

/**
* A hook to get the pattern categories that authors can assign to a pattern.
*
* The list of selectable slugs comes from the server, so that every category picker shows the same options.
*
* @return {Object[]} Category terms, sorted by name.
*/
export default function usePatternCategories() {
const terms = useSelect(
( select ) => select( coreStore ).getEntityRecords( 'taxonomy', CATEGORY_SLUG, QUERY ),
[]
);

return useMemo(
() =>
terms
? filterSelectableCategories( terms, wporgBlockPattern.categorySlugs || EMPTY_ARRAY )
: EMPTY_ARRAY,
[ terms ]
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,45 @@
remove_filter( 'the_content', 'do_shortcode', 11 );
add_filter( 'the_content', __NAMESPACE__ . '\do_shortcode_except_in_patterns', 11 );

/**
* Get the slugs of the categories that authors can assign to a pattern.
*
* This list mirrors the pattern categories registered by WordPress core. Other terms in the taxonomy
* (for example, retired categories like "Columns", or "Featured") are kept on existing patterns, but
* are not offered in the pattern creator.
*
* @see https://github.com/WordPress/wordpress-develop/blob/trunk/src/wp-includes/block-patterns.php
*
* @return string[] List of term slugs in the `wporg-pattern-category` taxonomy.
*/
function get_selectable_category_slugs() {
$slugs = array(
'about',
'audio',
'banner',
'call-to-action',
'contact',
'footer',
'gallery',
'header',
'portfolio',
'query', // "Posts".
'services',
'team',
'testimonials',
'text',
'video',
'wireframe',
);

/**
* Filters the category slugs that authors can assign to a pattern.
*
* @param string[] $slugs List of term slugs.
*/
return apply_filters( 'wporg_pattern_selectable_category_slugs', $slugs );
}

/**
* Registers post types and associated taxonomies, meta data, etc.
*/
Expand Down
Loading
Loading