Skip to content

Add Command Palette support #121

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 41 commits into from
May 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
5215215
First attempt at adding command palette support
priethor Apr 17, 2025
d6006fe
Command polishing
priethor Apr 17, 2025
4ec2af9
Add commands for registered CPTs
priethor Apr 17, 2025
18e250f
Refactor
priethor Apr 17, 2025
77bdff1
Refactor to use wp.scf
priethor Apr 17, 2025
b8894cf
Refactor
priethor Apr 18, 2025
6fce2a9
Refactor to pass the data in acf.data.customPostTypes
priethor Apr 18, 2025
f28d69d
Fix redirections
priethor Apr 18, 2025
6e4431c
Check capabilities before registering comands
priethor Apr 23, 2025
65485ac
Filter post types that don't enable the setting "show in UI"
priethor Apr 23, 2025
b06b8a7
Redo webpack config linting
priethor Apr 23, 2025
f5a6cba
Redo webpack linting
priethor Apr 23, 2025
4c6f5da
Apply linting
priethor Apr 24, 2025
c592beb
Polish
priethor Apr 24, 2025
3907d63
Refactor
priethor Apr 24, 2025
60b8184
Fix coding standards in comments
priethor Apr 24, 2025
af46e4a
Ensure we only register the commands when the palette is available
priethor Apr 24, 2025
b2d95ab
Refactor to remove scf and acf prefixes, organizing files in folders …
priethor Apr 24, 2025
101de27
Optimize return early
priethor Apr 25, 2025
4b6a953
Remove unnecessary script registration
priethor Apr 25, 2025
1a62407
Use the `scf` prefix in admin commands.
priethor Apr 25, 2025
47c480c
Use `scf` prefix for CPT commands
priethor Apr 25, 2025
9bed553
Only pass CPTs to the frontend when the array is not empty
priethor Apr 25, 2025
dfa0104
CPT commands: only add label as a keyword if not empty
priethor Apr 25, 2025
54176c5
Polish
priethor Apr 25, 2025
a23722c
Refactor the depencies to user import statements
priethor Apr 25, 2025
887f2cf
Optimize scripts loading and execution
priethor Apr 25, 2025
9944ea2
Update package-lock.json
priethor Apr 25, 2025
4def381
Slight refactor
priethor Apr 25, 2025
1df01fe
Assets: remove redundant registrations, prefix registered scritps
priethor Apr 25, 2025
ccb0edc
Remove redundant admin check
priethor Apr 28, 2025
b5f9199
Remove unnecesary checking of `acf_get_acf_post_types`
priethor Apr 28, 2025
71342ac
Change the priority queue to a simpler `requestIdleCallback`
priethor Apr 28, 2025
57ea58c
Update assets/src/js/commands/custom-post-type-commands.js
priethor May 12, 2025
7f57f51
Improved URL building
priethor May 14, 2025
3f99a4e
Fix command labels for better i18n
priethor May 14, 2025
ff6bb99
Simplify label calculation leveraging WP core functions
priethor May 14, 2025
3b547c3
Remove redundant client-side label calculation
priethor May 14, 2025
2af1415
Merge remote-tracking branch 'origin/trunk' into add/command-palette-…
priethor May 14, 2025
857b84f
Fixed `@since` annotations to follow the steps of #129
priethor May 14, 2025
85cd929
Switch to using the proper labels for the commands
priethor May 14, 2025
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
206 changes: 206 additions & 0 deletions assets/src/js/commands/admin-commands.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
/**
* Admin Commands
*
* Core WordPress commands for Secure Custom Fields administration.
* This file registers navigation commands for all primary SCF admin screens,
* enabling quick access through the WordPress commands interface (Cmd+K / Ctrl+K).
*
* @since SCF 6.5.0
*/

/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import { createElement } from '@wordpress/element';
import { Icon } from '@wordpress/components';
import { dispatch } from '@wordpress/data';
import { addQueryArgs } from '@wordpress/url';

/**
* Register admin commands for SCF
*/
const registerAdminCommands = () => {
if ( ! dispatch( 'core/commands' ) || ! window.acf?.data ) {
return;
}

const commandStore = dispatch( 'core/commands' );
const adminUrl = window.acf?.data?.admin_url || '';

const commands = [
{
name: 'field-groups',
label: __( 'Field Groups', 'secure-custom-fields' ),
url: 'edit.php',
urlArgs: { post_type: 'acf-field-group' },
icon: 'layout',
description: __(
'SCF: View and manage custom field groups',
'secure-custom-fields'
),
keywords: [
'acf',
'custom fields',
'field editor',
'manage fields',
],
},
{
name: 'new-field-group',
label: __( 'Create New Field Group', 'secure-custom-fields' ),
url: 'post-new.php',
urlArgs: { post_type: 'acf-field-group' },
icon: 'plus',
description: __(
'SCF: Create a new field group to organize custom fields',
'secure-custom-fields'
),
keywords: [
'add',
'new',
'create',
'field group',
'custom fields',
],
},
{
name: 'post-types',
label: __( 'Post Types', 'secure-custom-fields' ),
url: 'edit.php',
urlArgs: { post_type: 'acf-post-type' },
icon: 'admin-post',
description: __(
'SCF: Manage custom post types',
'secure-custom-fields'
),
keywords: [ 'cpt', 'content types', 'manage post types' ],
},
{
name: 'new-post-type',
label: __( 'Create New Post Type', 'secure-custom-fields' ),
url: 'post-new.php',
urlArgs: { post_type: 'acf-post-type' },
icon: 'plus',
description: __(
'SCF: Create a new custom post type',
'secure-custom-fields'
),
keywords: [ 'add', 'new', 'create', 'cpt', 'content type' ],
},
{
name: 'taxonomies',
label: __( 'Taxonomies', 'secure-custom-fields' ),
url: 'edit.php',
urlArgs: { post_type: 'acf-taxonomy' },
icon: 'category',
description: __(
'SCF: Manage custom taxonomies for organizing content',
'secure-custom-fields'
),
keywords: [ 'categories', 'tags', 'terms', 'custom taxonomies' ],
},
{
name: 'new-taxonomy',
label: __( 'Create New Taxonomy', 'secure-custom-fields' ),
url: 'post-new.php',
urlArgs: { post_type: 'acf-taxonomy' },
icon: 'plus',
description: __(
'SCF: Create a new custom taxonomy',
'secure-custom-fields'
),
keywords: [
'add',
'new',
'create',
'taxonomy',
'categories',
'tags',
],
},
{
name: 'options-pages',
label: __( 'Options Pages', 'secure-custom-fields' ),
url: 'edit.php',
urlArgs: { post_type: 'acf-ui-options-page' },
icon: 'admin-settings',
description: __(
'SCF: Manage custom options pages for global settings',
'secure-custom-fields'
),
keywords: [ 'settings', 'global options', 'site options' ],
},
{
name: 'new-options-page',
label: __( 'Create New Options Page', 'secure-custom-fields' ),
url: 'post-new.php',
urlArgs: { post_type: 'acf-ui-options-page' },
icon: 'plus',
description: __(
'SCF: Create a new custom options page',
'secure-custom-fields'
),
keywords: [ 'add', 'new', 'create', 'options', 'settings page' ],
},
{
name: 'tools',
label: __( 'SCF Tools', 'secure-custom-fields' ),
url: 'admin.php',
urlArgs: { page: 'acf-tools' },
icon: 'admin-tools',
description: __(
'SCF: Access SCF utility tools',
'secure-custom-fields'
),
keywords: [ 'utilities', 'import export', 'json' ],
},
{
name: 'import',
label: __( 'Import SCF Data', 'secure-custom-fields' ),
url: 'admin.php',
urlArgs: { page: 'acf-tools', tool: 'import' },
icon: 'upload',
description: __(
'SCF: Import field groups, post types, taxonomies, and options pages',
'secure-custom-fields'
),
keywords: [ 'upload', 'json', 'migration', 'transfer' ],
},
{
name: 'export',
label: __( 'Export SCF Data', 'secure-custom-fields' ),
url: 'admin.php',
urlArgs: { page: 'acf-tools', tool: 'export' },
icon: 'download',
description: __(
'SCF: Export field groups, post types, taxonomies, and options pages',
'secure-custom-fields'
),
keywords: [ 'download', 'json', 'backup', 'migration' ],
},
];

commands.forEach( ( command ) => {
commandStore.registerCommand( {
name: 'scf/' + command.name,
label: command.label,
icon: createElement( Icon, { icon: command.icon } ),
context: 'admin',
description: command.description,
keywords: command.keywords,
callback: ( { close } ) => {
document.location = command.urlArgs
? addQueryArgs( adminUrl + command.url, command.urlArgs )
: adminUrl + command.url;
close();
},
} );
} );
};

if ( 'requestIdleCallback' in window ) {
window.requestIdleCallback( registerAdminCommands, { timeout: 500 } );
} else {
setTimeout( registerAdminCommands, 500 );
}
96 changes: 96 additions & 0 deletions assets/src/js/commands/custom-post-type-commands.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/**
* Custom Post Type Commands
*
* Dynamic commands for user-created custom post types in Secure Custom Fields.
* This file generates navigation commands for each registered post type that
* the current user has access to, creating both "View All" and "Add New" commands.
*
* Post type data is provided via acf.data.customPostTypes, which is populated
* by the PHP side after capability checks ensure the user has appropriate access.
*
* @since SCF 6.5.0
*/

/**
* WordPress dependencies
*/
import { __, sprintf } from '@wordpress/i18n';
import { createElement } from '@wordpress/element';
import { Icon } from '@wordpress/components';
import { dispatch } from '@wordpress/data';
import { addQueryArgs } from '@wordpress/url';

/**
* Register custom post type commands
*/
const registerPostTypeCommands = () => {
// Only proceed when WordPress commands API and there are custom post types accessible
if (
! dispatch( 'core/commands' ) ||
! window.acf?.data?.customPostTypes?.length
) {
return;
}

const commandStore = dispatch( 'core/commands' );
const adminUrl = window.acf.data.admin_url || '';
const postTypes = window.acf.data.customPostTypes;

postTypes.forEach( ( postType ) => {
// Skip invalid post types or those missing required labels
if ( ! postType?.name || ! postType?.all_items || ! postType?.add_new_item ) {
return;
}

// Register "View All" command for this post type
commandStore.registerCommand( {
name: `scf/cpt-${ postType.name }`,
label: postType.all_items,
icon: createElement( Icon, { icon: 'admin-page' } ),
context: 'admin',
description: postType.all_items,
keywords: [
'post type',
'content',
'cpt',
postType.name,
postType.label,
].filter( Boolean ),
callback: ( { close } ) => {
document.location = addQueryArgs(adminUrl + 'edit.php', {
post_type: postType.name
});
close();
},
} );

// Register "Add New" command for this post type
commandStore.registerCommand( {
name: `scf/new-${ postType.name }`,
label: postType.add_new_item,
icon: createElement( Icon, { icon: 'plus' } ),
context: 'admin',
description: postType.add_new_item,
keywords: [
'add',
'new',
'create',
'content',
postType.name,
...( postType.label ? [ postType.label ] : [] ),
],
callback: ( { close } ) => {
document.location = addQueryArgs(adminUrl + 'post-new.php', {
post_type: postType.name
});
close();
},
} );
} );
};

if ( 'requestIdleCallback' in window ) {
window.requestIdleCallback( registerPostTypeCommands, { timeout: 500 } );
} else {
setTimeout( registerPostTypeCommands, 500 );
}
80 changes: 80 additions & 0 deletions includes/admin/admin-commands.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<?php
/**
* SCF Commands Integration
*
* @package Secure Custom Fields
*/

if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}

/**
* Initializes SCF commands integration
*
* This function handles the integration with WordPress Commands (Cmd+K / Ctrl+K),
* providing navigation commands for SCF admin pages and custom post types.
*
* The implementation follows these principles:
* 1. Only loads in screens where WordPress commands are available.
* 2. Performs capability checks to ensure users only see commands they can access.
* 3. Core administrative commands are only shown to users with SCF admin capabilities.
* 4. Custom post type commands are conditionally shown based on edit_posts capability
* for each specific post type.
* 5. Post types must have UI enabled (show_ui setting) to appear in commands.
*
* @since SCF 6.5.0
*/
function acf_commands_init() {
// Ensure we only load our commands where the WordPress commands API is available.
if ( ! wp_script_is( 'wp-commands', 'registered' ) ) {
return;
}

$custom_post_types = array();

$scf_post_types = acf_get_acf_post_types();

foreach ( $scf_post_types as $post_type ) {
// Skip if post type name is not set (defensive) or post type is inactive.
if ( empty( $post_type['post_type'] ) || ( isset( $post_type['active'] ) && ! $post_type['active'] ) ) {
continue;
}

$post_type_obj = get_post_type_object( $post_type['post_type'] );
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I ask not knowing acf_get_acf_post_types: is it ever possible to have a key returned by acf_get_acf_post_types whose type object cannot be found? When?

Copy link
Contributor Author

@priethor priethor May 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In theory it could in very edge cases, but we already checked empty( $post_type['post_type'] ) here, in which case this line wouldn't execute. The later checking of $post_type_obj also ensures this line executes properly.


// Three conditions must be met to include this post type in the commands:
// 1. Post type object must exist
// 2. Current user must have permission to edit posts of this type.
// 3. Post type must have admin UI enabled (show_ui setting).
if ( $post_type_obj &&
current_user_can( $post_type_obj->cap->edit_posts ) &&
$post_type_obj->show_ui ) {

$labels = get_post_type_labels( $post_type_obj );

$custom_post_types[] = array(
'name' => $post_type['post_type'],
'all_items' => $labels->all_items,
'add_new_item' => $labels->add_new_item,
'icon' => $post_type['menu_icon'] ?? '',
);
}
}

if ( ! empty( $custom_post_types ) ) {
acf_localize_data(
array(
'customPostTypes' => $custom_post_types,
)
);
wp_enqueue_script( 'scf-commands-custom-post-types' );
}

// Only load admin commands if user has SCF admin capabilities.
if ( current_user_can( acf_get_setting( 'capability' ) ) ) {
wp_enqueue_script( 'scf-commands-admin' );
}
}

add_action( 'admin_enqueue_scripts', 'acf_commands_init' );
Loading
Loading