Skip to content

Commit

Permalink
QuickEdit: Add slug field control (#65196)
Browse files Browse the repository at this point in the history
Co-authored-by: gigitux <gigitux@git.wordpress.org>
Co-authored-by: oandregal <oandregal@git.wordpress.org>
Co-authored-by: jameskoster <jameskoster@git.wordpress.org>
  • Loading branch information
4 people authored Oct 23, 2024
1 parent 609d573 commit 1ca8001
Show file tree
Hide file tree
Showing 11 changed files with 258 additions and 2 deletions.
21 changes: 20 additions & 1 deletion packages/edit-site/src/components/post-edit/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,19 @@ function PostEditForm( { postType, postId } ) {
'title',
'author',
'date',
'slug',
'comment_status',
],
};

const fieldsWithBulkEditSupport = [
'title',
'status',
'date',
'author',
'comment_status',
];

const onChange = ( edits ) => {
for ( const id of ids ) {
if (
Expand Down Expand Up @@ -106,7 +116,16 @@ function PostEditForm( { postType, postId } ) {
<DataForm
data={ ids.length === 1 ? record : multiEdits }
fields={ fields }
form={ form }
form={
ids.length === 1
? form
: {
...form,
fields: form.fields.filter( ( field ) =>
fieldsWithBulkEditSupport.includes( field )
),
}
}
onChange={ onChange }
/>
</VStack>
Expand Down
3 changes: 2 additions & 1 deletion packages/edit-site/src/components/post-fields/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import clsx from 'clsx';
*/
import { __, sprintf } from '@wordpress/i18n';
import { decodeEntities } from '@wordpress/html-entities';
import { featuredImageField } from '@wordpress/fields';
import { featuredImageField, slugField } from '@wordpress/fields';
import {
createInterpolateElement,
useMemo,
Expand Down Expand Up @@ -320,6 +320,7 @@ function usePostFields( viewType ) {
return <time>{ getFormattedDate( item.date ) }</time>;
},
},
slugField,
{
id: 'comment_status',
label: __( 'Discussion' ),
Expand Down
1 change: 1 addition & 0 deletions packages/edit-site/src/style.scss
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
@import "../../dataviews/src/style.scss";
@import "../../fields/src/styles.scss";
@import "../../fields/src/fields/featured-image/style.scss";

@import "./components/add-new-template/style.scss";
Expand Down
4 changes: 4 additions & 0 deletions packages/fields/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ Undocumented declaration.

Undocumented declaration.

### slugField

Undocumented declaration.

### titleField

Undocumented declaration.
Expand Down
1 change: 1 addition & 0 deletions packages/fields/src/fields/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { default as slugField } from './slug';
export { default as titleField } from './title';
export { default as orderField } from './order';
export { default as featuredImageField } from './featured-image';
23 changes: 23 additions & 0 deletions packages/fields/src/fields/slug/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
* WordPress dependencies
*/
import type { Field } from '@wordpress/dataviews';

/**
* Internal dependencies
*/
import type { BasePost } from '../../types';
import { __ } from '@wordpress/i18n';
import SlugEdit from './slug-edit';
import SlugView from './slug-view';

const slugField: Field< BasePost > = {
id: 'slug',
type: 'text',
label: __( 'Slug' ),
getValue: ( { item } ) => item.slug,
Edit: SlugEdit,
render: SlugView,
};

export default slugField;
156 changes: 156 additions & 0 deletions packages/fields/src/fields/slug/slug-edit.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
/**
* WordPress dependencies
*/
import {
Button,
ExternalLink,
__experimentalInputControl as InputControl,
__experimentalInputControlPrefixWrapper as InputControlPrefixWrapper,
__experimentalVStack as VStack,
} from '@wordpress/components';
import { copySmall } from '@wordpress/icons';
import { useCopyToClipboard, useInstanceId } from '@wordpress/compose';
import { useDispatch } from '@wordpress/data';
import { useCallback, useEffect, useRef } from '@wordpress/element';
import { store as noticesStore } from '@wordpress/notices';
import { safeDecodeURIComponent } from '@wordpress/url';
import type { DataFormControlProps } from '@wordpress/dataviews';
import { __ } from '@wordpress/i18n';

/**
* Internal dependencies
*/
import type { BasePost } from '../../types';

const SlugEdit = ( {
field,
onChange,
data,
}: DataFormControlProps< BasePost > ) => {
const { id } = field;

const slug = field.getValue( { item: data } ) ?? '';
const permalinkTemplate = data.permalink_template || '';
const PERMALINK_POSTNAME_REGEX = /%(?:postname|pagename)%/;
const [ prefix, suffix ] = permalinkTemplate.split(
PERMALINK_POSTNAME_REGEX
);
const permalinkPrefix = prefix;
const permalinkSuffix = suffix;
const isEditable = PERMALINK_POSTNAME_REGEX.test( permalinkTemplate );
const originalSlug = useRef( slug );
const slugToDisplay = slug || originalSlug.current;
const permalink = isEditable
? `${ permalinkPrefix }${ slugToDisplay }${ permalinkSuffix }`
: safeDecodeURIComponent( data.link || '' );

useEffect( () => {
if ( slug && originalSlug.current === undefined ) {
originalSlug.current = slug;
}
}, [ slug ] );

const onChangeControl = useCallback(
( newValue?: string ) =>
onChange( {
[ id ]: newValue,
} ),
[ id, onChange ]
);

const { createNotice } = useDispatch( noticesStore );

const copyButtonRef = useCopyToClipboard( permalink, () => {
createNotice( 'info', __( 'Copied Permalink to clipboard.' ), {
isDismissible: true,
type: 'snackbar',
} );
} );

const postUrlSlugDescriptionId =
'editor-post-url__slug-description-' + useInstanceId( SlugEdit );

return (
<fieldset className="fields-controls__slug">
{ isEditable && (
<VStack>
<VStack spacing="0px">
<span>
{ __(
'Customize the last part of the Permalink.'
) }
</span>
<ExternalLink href="https://wordpress.org/documentation/article/page-post-settings-sidebar/#permalink">
{ __( 'Learn more' ) }
</ExternalLink>
</VStack>
<InputControl
__next40pxDefaultSize
prefix={
<InputControlPrefixWrapper>
/
</InputControlPrefixWrapper>
}
suffix={
<Button
__next40pxDefaultSize
icon={ copySmall }
ref={ copyButtonRef }
label={ __( 'Copy' ) }
/>
}
label={ __( 'Link' ) }
hideLabelFromVision
value={ slug }
autoComplete="off"
spellCheck="false"
type="text"
className="fields-controls__slug-input"
onChange={ ( newValue?: string ) => {
onChangeControl( newValue );
} }
onBlur={ () => {
if ( slug === '' ) {
onChangeControl( originalSlug.current );
}
} }
aria-describedby={ postUrlSlugDescriptionId }
help={
<>
<p className="fields-controls__slug-help">
<span className="fields-controls__slug-help-visual-label">
{ __( 'Permalink:' ) }
</span>
<ExternalLink
className="fields-controls__slug-help-link"
href={ permalink }
>
<span className="fields-controls__slug-help-prefix">
{ permalinkPrefix }
</span>
<span className="fields-controls__slug-help-slug">
{ slugToDisplay }
</span>
<span className="fields-controls__slug-help-suffix">
{ permalinkSuffix }
</span>
</ExternalLink>
</p>
</>
}
/>
</VStack>
) }
{ ! isEditable && (
<ExternalLink
className="fields-controls__slug-help"
href={ permalink }
>
{ permalink }
</ExternalLink>
) }
</fieldset>
);
};

export default SlugEdit;
26 changes: 26 additions & 0 deletions packages/fields/src/fields/slug/slug-view.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* WordPress dependencies
*/
import { useEffect, useRef } from '@wordpress/element';

/**
* Internal dependencies
*/
import type { BasePost } from '../../types';

const SlugView = ( { item }: { item: BasePost } ) => {
const slug = item.slug;
const originalSlug = useRef( slug );

useEffect( () => {
if ( slug && originalSlug.current === undefined ) {
originalSlug.current = slug;
}
}, [ slug ] );

const slugToDisplay = slug || originalSlug.current;

return `/${ slugToDisplay ?? '' }`;
};

export default SlugView;
22 changes: 22 additions & 0 deletions packages/fields/src/fields/slug/style.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
.fields-controls__slug {
.fields-controls__slug-external-icon {
margin-left: 5ch;
}

.fields-controls__slug-input input.components-input-control__input {
padding-inline-start: 0 !important;
}

.fields-controls__slug-help-link {
word-break: break-word;
}

.fields-controls__slug-help {
display: flex;
flex-direction: column;

.fields-controls__slug-help-slug {
font-weight: 600;
}
}
}
1 change: 1 addition & 0 deletions packages/fields/src/styles.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@import "./fields/slug/style.scss";
2 changes: 2 additions & 0 deletions packages/fields/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ export interface BasePost extends CommonPost {
menu_order?: number;
ping_status?: 'open' | 'closed';
link?: string;
slug?: string;
permalink_template?: string;
}

export interface Template extends CommonPost {
Expand Down

0 comments on commit 1ca8001

Please sign in to comment.