Skip to content
Open
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
4 changes: 4 additions & 0 deletions packages/block-editor/src/components/link-control/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ function LinkControl( {
hasTextControl = false,
renderControlBottom = null,
handleEntities = false,
showCreateSuggestionInDropdown,
} ) {
if ( withCreateSuggestion === undefined && createSuggestion ) {
withCreateSuggestion = true;
Expand Down Expand Up @@ -711,6 +712,9 @@ function LinkControl( {
helpTextId={ helpTextId }
/>
}
showCreateSuggestionInDropdown={
showCreateSuggestionInDropdown
}
/>
{ isEntity && helpTextId && (
<p
Expand Down
169 changes: 169 additions & 0 deletions packages/block-editor/src/components/link-control/page-creator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
/**
* WordPress dependencies
*/
import { useState, useRef, useEffect } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import {
Button,
TextControl,
Notice,
CheckboxControl,
__experimentalVStack as VStack,

Check failure on line 11 in packages/block-editor/src/components/link-control/page-creator.js

View workflow job for this annotation

GitHub Actions / All (Node.js 24 on Linux)

Use `Stack` from `@wordpress/ui` instead.
__experimentalHStack as HStack,

Check failure on line 12 in packages/block-editor/src/components/link-control/page-creator.js

View workflow job for this annotation

GitHub Actions / All (Node.js 24 on Linux)

Use `Stack` from `@wordpress/ui` instead.
} from '@wordpress/components';
import { chevronLeftSmall } from '@wordpress/icons';

function generateSlug( text ) {
const cleanSlug = text
.toLowerCase()
.replace( /[^a-z0-9\s-]/g, '' )
.replace( /\s+/g, '-' )
.replace( /-+/g, '-' )
.trim();

return cleanSlug ? '/' + cleanSlug : '';
}

/**
* Inline page creation form for LinkControl.
*
* Page creation is delegated to the consumer via onCreateSuggestion, which
* already carries the saveEntityRecord call.
*
* @param {Object} props
* @param {string} props.initialTitle Pre-populated from the search input value.
* @param {Function} props.onCreateSuggestion Consumer-provided async creator.
* @param {Function} props.onPageCreated Called with the resulting link object on success.
* @param {Function} props.onCancel Called when the user cancels.
*/
export function LinkControlPageCreator( {
initialTitle = '',
onCreateSuggestion,
onPageCreated,
onCancel,
} ) {
const [ title, setTitle ] = useState( initialTitle );
const [ isCreating, setIsCreating ] = useState( false );
const [ errorMessage, setErrorMessage ] = useState( null );
const [ publishImmediately, setPublishImmediately ] = useState( true );
const [ slug, setSlug ] = useState( generateSlug( initialTitle ) );
const [ isSlugDirty, setIsSlugDirty ] = useState( false );
const backButtonRef = useRef();

useEffect( () => {
backButtonRef.current?.focus();
}, [] );

const handleTitleChange = ( newTitle ) => {
setTitle( newTitle );
if ( ! isSlugDirty ) {
setSlug( generateSlug( newTitle ) );
}
};

const handleSlugChange = ( newSlug ) => {
setSlug( newSlug );
setIsSlugDirty( true );
};

const handleCreate = async () => {
if ( ! title?.trim() ) {
return;
}

setIsCreating( true );
setErrorMessage( null );
try {
const suggestion = await onCreateSuggestion(
title.trim(),
publishImmediately,
slug.trim()
);
if ( suggestion?.url ) {
onPageCreated( suggestion );
}
} catch ( _e ) {

Check failure on line 85 in packages/block-editor/src/components/link-control/page-creator.js

View workflow job for this annotation

GitHub Actions / All (Node.js 24 on Linux)

'_e' is defined but never used.
setErrorMessage(
__( 'There was an error creating the page. Please try again.' )
);
setIsCreating( false );
}
};

return (
<div className="block-editor-link-control__page-creator">
<Button
ref={ backButtonRef }
className="block-editor-link-control__page-creator__back"
icon={ chevronLeftSmall }
onClick={ onCancel }
size="small"
>
{ __( 'Back' ) }
</Button>
<div className="block-editor-link-control__page-creator__inner">
{ errorMessage && (
<Notice status="error" isDismissible={ false }>
{ errorMessage }
</Notice>
) }
<VStack spacing={ 4 }>
<TextControl
__next40pxDefaultSize
label={ __( 'Page title' ) }
value={ title }
onChange={ handleTitleChange }
placeholder={ __( 'Enter page title' ) }
onKeyDown={ ( event ) => {
if ( event.key === 'Enter' ) {
event.preventDefault();
handleCreate();
}
} }
disabled={ isCreating }
/>
<TextControl
__next40pxDefaultSize
label={ __( 'URL slug' ) }
value={ slug }
onChange={ handleSlugChange }
help={ __(
'Auto-generated from the title. Edit to customise.'
) }
disabled={ isCreating }
/>
<CheckboxControl
label={ __( 'Publish immediately' ) }
help={ __(
'If unchecked, the page will be created as a draft.'
) }
checked={ publishImmediately }
onChange={ setPublishImmediately }
disabled={ isCreating }
/>
<HStack justify="right" spacing={ 2 }>
<Button
__next40pxDefaultSize
variant="tertiary"
onClick={ onCancel }
disabled={ isCreating }
accessibleWhenDisabled
>
{ __( 'Cancel' ) }
</Button>
<Button
__next40pxDefaultSize
variant="primary"
onClick={ handleCreate }
isBusy={ isCreating }
disabled={ ! title?.trim() || isCreating }
accessibleWhenDisabled
>
{ isCreating ? __( 'Creating…' ) : __( 'Create' ) }
</Button>
</HStack>
</VStack>
</div>
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
*/
import { forwardRef, useState } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import { plus } from '@wordpress/icons';
import { Button } from '@wordpress/components';
import deprecated from '@wordpress/deprecated';
import { LinkControlPageCreator } from './page-creator';

/**
* Internal dependencies
Expand Down Expand Up @@ -46,13 +49,14 @@ const LinkControlSearchInput = forwardRef(
suffix,
isEntity = false,
customValidity: customValidityProp,
showCreateSuggestionInDropdown = false,
},
ref
) => {
const genericSearchHandler = useSearchHandler(
suggestionsQuery,
allowDirectEntry,
withCreateSuggestion,
withCreateSuggestion && showCreateSuggestionInDropdown,
withURLSuggestion
);

Expand All @@ -61,6 +65,7 @@ const LinkControlSearchInput = forwardRef(
: noopSearchHandler;

const [ focusedSuggestion, setFocusedSuggestion ] = useState();
const [ isCreatingPage, setIsCreatingPage ] = useState( false );

/**
* Handles the user moving between different suggestions. Does not handle
Expand Down Expand Up @@ -129,6 +134,17 @@ const LinkControlSearchInput = forwardRef(
? _placeholder
: __( 'Link' );

if ( withCreateSuggestion && isCreatingPage ) {
return (
<LinkControlPageCreator
initialTitle={ value || '' }
onCreateSuggestion={ onCreateSuggestion }
onPageCreated={ () => setIsCreatingPage( false ) }
onCancel={ () => setIsCreatingPage( false ) }
/>
);
}

return (
<div className="block-editor-link-control__search-input-container">
<URLInput
Expand Down Expand Up @@ -169,6 +185,18 @@ const LinkControlSearchInput = forwardRef(
suffix={ suffix }
disabled={ isEntity }
/>
{ withCreateSuggestion && ! showCreateSuggestionInDropdown && (
<Button
className="block-editor-link-control__search-create has-text"
icon={ plus }
onClick={ () => setIsCreatingPage( true ) }
__next40pxDefaultSize
>
{ value
? createSuggestionButtonText( value )
: __( 'Create page' ) }
</Button>
) }
{ children }
</div>
);
Expand Down
31 changes: 31 additions & 0 deletions packages/block-editor/src/components/link-control/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,22 @@ $block-editor-link-control-number-of-actions: 1;
}
}
}

&__page-creator {
max-width: 350px;
min-width: auto;
width: 90vw;
padding-top: $grid-unit-10;

&__back {
margin-left: $grid-unit-10;
text-transform: uppercase;
}

&__inner {
padding: $grid-unit-20;
}
}
}

// Provides positioning context for reset button. Without this then when an
Expand Down Expand Up @@ -304,6 +320,21 @@ $block-editor-link-control-number-of-actions: 1;

.block-editor-link-control__search-create {
align-items: center; // align text with icon.
width: 100%;
border-top: 1px solid $gray-200;
padding: $grid-unit-10 $grid-unit-15;
color: $gray-900;

&:hover,
&:focus {
background-color: $gray-100;
}

mark {
font-weight: 600;
color: inherit;
background-color: transparent;
}

.block-editor-link-control__preview-title {
margin-bottom: 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@ export default function useCreatePage( handleCreatePage ) {
const [ isCreatingPage, setIsCreatingPage ] = useState( false );
const [ errorMessage, setErrorMessage ] = useState( null );

const createPage = async function ( suggestionTitle ) {
const createPage = async function (
suggestionTitle,
publishImmediately,
Slug
) {
setIsCreatingPage( true );
setErrorMessage( null );

Expand All @@ -19,7 +23,13 @@ export default function useCreatePage( handleCreatePage ) {
cancelableCreateSuggestion.current = makeCancelable(
// Using Promise.resolve to allow createSuggestion to return a
// non-Promise based value.
Promise.resolve( handleCreatePage( suggestionTitle ) )
Promise.resolve(
handleCreatePage(
suggestionTitle,
publishImmediately,
Slug
)
)
);

return await cancelableCreateSuggestion.current.promise;
Expand Down
13 changes: 9 additions & 4 deletions packages/block-library/src/button/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -204,11 +204,16 @@ function ButtonEdit( props ) {
[ context, isSelected, metadata?.bindings?.url ]
);

async function handleCreate( pageTitle ) {
const page = await createPageEntity( {
async function handleCreate( pageTitle, publishImmediately, Slug ) {
const payload = {
title: pageTitle,
status: 'draft',
} );
status: publishImmediately ? 'publish' : 'draft',
};

if ( Slug ) {
payload.slug = Slug;
}
const page = await createPageEntity( payload );

return {
id: page.id,
Expand Down
13 changes: 9 additions & 4 deletions packages/format-library/src/link/inline.js
Original file line number Diff line number Diff line change
Expand Up @@ -251,11 +251,16 @@ function InlineLinkUI( {
},
} );

async function handleCreate( pageTitle ) {
const page = await createPageEntity( {
async function handleCreate( pageTitle, publishImmediately, Slug ) {
const payload = {
title: pageTitle,
status: 'draft',
} );
status: publishImmediately ? 'publish' : 'draft',
};

if ( Slug ) {
payload.slug = Slug;
}
const page = await createPageEntity( payload );

return {
id: page.id,
Expand Down
Loading