diff --git a/package-lock.json b/package-lock.json index c13e4cf2096cd..9d3bc7007499e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -53835,6 +53835,7 @@ "@wordpress/editor": "file:../editor", "@wordpress/element": "file:../element", "@wordpress/escape-html": "file:../escape-html", + "@wordpress/fields": "file:../fields", "@wordpress/hooks": "file:../hooks", "@wordpress/html-entities": "file:../html-entities", "@wordpress/i18n": "file:../i18n", @@ -68685,6 +68686,7 @@ "@wordpress/editor": "file:../editor", "@wordpress/element": "file:../element", "@wordpress/escape-html": "file:../escape-html", + "@wordpress/fields": "file:../fields", "@wordpress/hooks": "file:../hooks", "@wordpress/html-entities": "file:../html-entities", "@wordpress/i18n": "file:../i18n", diff --git a/packages/edit-site/package.json b/packages/edit-site/package.json index ef1cdaaa6a5c7..dec5761a62806 100644 --- a/packages/edit-site/package.json +++ b/packages/edit-site/package.json @@ -48,6 +48,7 @@ "@wordpress/editor": "file:../editor", "@wordpress/element": "file:../element", "@wordpress/escape-html": "file:../escape-html", + "@wordpress/fields": "file:../fields", "@wordpress/hooks": "file:../hooks", "@wordpress/html-entities": "file:../html-entities", "@wordpress/i18n": "file:../i18n", diff --git a/packages/edit-site/src/components/post-edit/index.js b/packages/edit-site/src/components/post-edit/index.js index 0ec63589d9767..cb405eea11a85 100644 --- a/packages/edit-site/src/components/post-edit/index.js +++ b/packages/edit-site/src/components/post-edit/index.js @@ -60,8 +60,24 @@ function PostEditForm( { postType, postId } ) { ); const form = { type: 'panel', - fields: [ 'title', 'status', 'date', 'author', 'comment_status' ], + fields: [ + 'title', + 'status', + 'date', + 'author', + 'slug', + 'comment_status', + ], }; + + const fieldsWithBulkEditSupport = [ + 'title', + 'status', + 'date', + 'author', + 'comment_status', + ]; + const onChange = ( edits ) => { for ( const id of ids ) { if ( @@ -95,7 +111,16 @@ function PostEditForm( { postType, postId } ) { + fieldsWithBulkEditSupport.includes( field ) + ), + } + } onChange={ onChange } /> diff --git a/packages/edit-site/src/components/post-fields/index.js b/packages/edit-site/src/components/post-fields/index.js index 9e59b23d61922..c9e83158ed073 100644 --- a/packages/edit-site/src/components/post-fields/index.js +++ b/packages/edit-site/src/components/post-fields/index.js @@ -26,6 +26,7 @@ import { import { __experimentalHStack as HStack, Icon } from '@wordpress/components'; import { useSelect } from '@wordpress/data'; import { useEntityRecords, store as coreStore } from '@wordpress/core-data'; +import { slugField } from '@wordpress/fields'; /** * Internal dependencies @@ -369,6 +370,7 @@ function usePostFields( viewType ) { return ; }, }, + slugField, { id: 'comment_status', label: __( 'Discussion' ), diff --git a/packages/edit-site/src/style.scss b/packages/edit-site/src/style.scss index 4e9e1071e3a77..e549857dbbaf9 100644 --- a/packages/edit-site/src/style.scss +++ b/packages/edit-site/src/style.scss @@ -1,4 +1,5 @@ @import "../../dataviews/src/style.scss"; +@import "../../fields/src/styles.scss"; @import "./components/add-new-template/style.scss"; @import "./components/block-editor/style.scss"; diff --git a/packages/fields/README.md b/packages/fields/README.md index b4e45103600da..cded27e5bd9d4 100644 --- a/packages/fields/README.md +++ b/packages/fields/README.md @@ -66,6 +66,10 @@ Undocumented declaration. Undocumented declaration. +### slugField + +Undocumented declaration. + ### titleField Undocumented declaration. diff --git a/packages/fields/src/fields/index.ts b/packages/fields/src/fields/index.ts index 63ff87842fa4c..f07896c9a49cc 100644 --- a/packages/fields/src/fields/index.ts +++ b/packages/fields/src/fields/index.ts @@ -1,2 +1,3 @@ +export { default as slugField } from './slug'; export { default as titleField } from './title'; export { default as orderField } from './order'; diff --git a/packages/fields/src/fields/slug/index.ts b/packages/fields/src/fields/slug/index.ts new file mode 100644 index 0000000000000..803e04bbfee52 --- /dev/null +++ b/packages/fields/src/fields/slug/index.ts @@ -0,0 +1,22 @@ +/** + * 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', + label: __( 'Link' ), + getValue: ( { item } ) => item.slug, + Edit: SlugEdit, + render: SlugView, +}; + +export default slugField; diff --git a/packages/fields/src/fields/slug/slug-edit.tsx b/packages/fields/src/fields/slug/slug-edit.tsx new file mode 100644 index 0000000000000..3def1d868fca9 --- /dev/null +++ b/packages/fields/src/fields/slug/slug-edit.tsx @@ -0,0 +1,141 @@ +/** + * WordPress dependencies + */ +import { + Button, + ExternalLink, + __experimentalInputControl as InputControl, + __experimentalInputControlPrefixWrapper as InputControlPrefixWrapper, + __experimentalVStack as VStack, +} from '@wordpress/components'; +import { copySmall } from '@wordpress/icons'; +import { useCopyToClipboard } 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 URL to clipboard.' ), { + isDismissible: true, + type: 'snackbar', + } ); + } ); + + return ( +
+ { isEditable && ( + + + + { __( 'Customize the last part of the URL.' ) } + + + { __( 'Learn more' ) } + + + + / + + } + suffix={ +
+ ); +}; + +export default SlugEdit; diff --git a/packages/fields/src/fields/slug/slug-view.tsx b/packages/fields/src/fields/slug/slug-view.tsx new file mode 100644 index 0000000000000..7448673c6a846 --- /dev/null +++ b/packages/fields/src/fields/slug/slug-view.tsx @@ -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; diff --git a/packages/fields/src/fields/slug/style.scss b/packages/fields/src/fields/slug/style.scss new file mode 100644 index 0000000000000..d705c2c38681b --- /dev/null +++ b/packages/fields/src/fields/slug/style.scss @@ -0,0 +1,18 @@ +.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 { + color: $gray-700; + + .fields-controls__slug-help-slug, + .fields-controls__slug-help-suffix { + font-weight: 600; + } + } +} diff --git a/packages/fields/src/styles.scss b/packages/fields/src/styles.scss new file mode 100644 index 0000000000000..cdb130337f1cd --- /dev/null +++ b/packages/fields/src/styles.scss @@ -0,0 +1 @@ +@import "./fields/slug/style.scss"; diff --git a/packages/fields/src/types.ts b/packages/fields/src/types.ts index a5ed9596b07df..d895195cb40af 100644 --- a/packages/fields/src/types.ts +++ b/packages/fields/src/types.ts @@ -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 {