Skip to content
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

Implement validation slug #66676

Closed
wants to merge 3 commits into from
Closed
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
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 2 additions & 5 deletions packages/dataviews/src/normalize-fields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,8 @@ export function normalizeFields< Item >(

const isValid =
field.isValid ??
function isValid( item, context ) {
return fieldTypeDefinition.isValid(
getValue( { item } ),
context
);
function isValid( value, item, context ) {
return fieldTypeDefinition.isValid( value, context );
};

const Edit = getControl( field, fieldTypeDefinition );
Expand Down
8 changes: 6 additions & 2 deletions packages/dataviews/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,11 @@ export type Field< Item > = {
/**
* Callback used to validate the field.
*/
isValid?: ( item: Item, context?: ValidationContext ) => boolean;
isValid?: (
value: any,
item?: Item,
context?: ValidationContext
) => boolean;

/**
* Callback used to decide if a field should be displayed.
Expand Down Expand Up @@ -167,7 +171,7 @@ export type NormalizedField< Item > = Field< Item > & {
render: ComponentType< DataViewRenderFieldProps< Item > >;
Edit: ComponentType< DataFormControlProps< Item > >;
sort: ( a: Item, b: Item, direction: SortDirection ) => number;
isValid: ( item: Item, context?: ValidationContext ) => boolean;
isValid: ( value: any, item: Item, context?: ValidationContext ) => boolean;
enableHiding: boolean;
enableSorting: boolean;
};
Expand Down
3 changes: 2 additions & 1 deletion packages/dataviews/src/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export function isItemValid< Item >(
fields.filter( ( { id } ) => !! form.fields?.includes( id ) )
);
return _fields.every( ( field ) => {
return field.isValid( item, { elements: field.elements } );
const value = field.getValue( { item } );
return field.isValid( value, item, { elements: field.elements } );
} );
}
5 changes: 4 additions & 1 deletion packages/edit-site/src/components/app/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import useInitEditedEntityFromURL from '../sync-state-with-url/use-init-edited-e
import useActiveRoute from '../layout/router';
import useSetCommandContext from '../../hooks/commands/use-set-command-context';
import { useRegisterSiteEditorRoutes } from '../site-editor-routes';
import { PostEditProvider } from '../post-edit/context';

const { RouterProvider } = unlock( routerPrivateApis );
const { GlobalStylesProvider } = unlock( editorPrivateApis );
Expand Down Expand Up @@ -59,7 +60,9 @@ export default function App() {
<GlobalStylesProvider>
<UnsavedChangesWarning />
<RouterProvider>
<AppLayout />
<PostEditProvider>
<AppLayout />
</PostEditProvider>
<PluginArea onError={ onPluginAreaError } />
</RouterProvider>
</GlobalStylesProvider>
Expand Down
23 changes: 23 additions & 0 deletions packages/edit-site/src/components/post-edit/context.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
* WordPress dependencies
*/
import { createContext, useContext, useState } from '@wordpress/element';

export const PostEditContext = createContext( {
isValidForm: false,
setIsValidForm: () => {},
} );

export const PostEditProvider = ( { children } ) => {
const [ isValidForm, setIsValidForm ] = useState( false );

return (
<PostEditContext.Provider value={ { isValidForm, setIsValidForm } }>
{ children }
</PostEditContext.Provider>
);
};

export const usePostEditContext = () => {
return useContext( PostEditContext );
};
12 changes: 11 additions & 1 deletion packages/edit-site/src/components/post-edit/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import clsx from 'clsx';
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import { DataForm } from '@wordpress/dataviews';
import { DataForm, isItemValid } from '@wordpress/dataviews';
import { useDispatch, useSelect } from '@wordpress/data';
import { store as coreDataStore } from '@wordpress/core-data';
import { __experimentalVStack as VStack } from '@wordpress/components';
Expand All @@ -20,6 +20,7 @@ import { privateApis as editorPrivateApis } from '@wordpress/editor';
import Page from '../page';
import usePostFields from '../post-fields';
import { unlock } from '../../lock-unlock';
import { usePostEditContext } from './context';

const { PostCardPanel } = unlock( editorPrivateApis );

Expand Down Expand Up @@ -96,6 +97,9 @@ function PostEditForm( { postType, postId } ) {
} ),
[ ids ]
);

const { setIsValidForm } = usePostEditContext();

const onChange = ( edits ) => {
for ( const id of ids ) {
if (
Expand All @@ -120,6 +124,12 @@ function PostEditForm( { postType, postId } ) {
...edits,
} ) );
}
const isValidForm = isItemValid(
{ ...record, ...edits },
fields,
form
);
setIsValidForm( isValidForm );
}
};
useEffect( () => {
Expand Down
6 changes: 6 additions & 0 deletions packages/edit-site/src/components/save-hub/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { check } from '@wordpress/icons';
*/
import SaveButton from '../save-button';
import { isPreviewingTheme } from '../../utils/is-previewing-theme';
import { usePostEditContext } from '../post-edit/context';

export default function SaveHub() {
const { isDisabled, isSaving } = useSelect( ( select ) => {
Expand All @@ -27,6 +28,11 @@ export default function SaveHub() {
( ! dirtyEntityRecords.length && ! isPreviewingTheme() ),
};
}, [] );

const { isValidForm } = usePostEditContext();

console.log( isValidForm );

return (
<HStack className="edit-site-save-hub" alignment="right" spacing={ 4 }>
<SaveButton
Expand Down
1 change: 1 addition & 0 deletions packages/fields/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
"@wordpress/warning": "*",
"change-case": "4.1.2",
"client-zip": "^2.4.5",
"clsx": "^2.1.1",
"remove-accents": "^0.5.0"
},
"peerDependencies": {
Expand Down
1 change: 1 addition & 0 deletions packages/fields/src/actions/reorder-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ function ReorderModal( {
}
}
const isSaveDisabled = ! isItemValid( item, fields, formOrderAction );
console.log( isSaveDisabled );
return (
<form onSubmit={ onOrder }>
<VStack spacing="5">
Expand Down
6 changes: 5 additions & 1 deletion packages/fields/src/fields/slug/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,16 @@ import type { BasePost } from '../../types';
import { __ } from '@wordpress/i18n';
import SlugEdit from './slug-edit';
import SlugView from './slug-view';
import { getSlug } from './utils';

const slugField: Field< BasePost > = {
id: 'slug',
type: 'text',
label: __( 'Slug' ),
getValue: ( { item } ) => item.slug,
getValue: ( { item } ) => getSlug( item ),
isValid: ( value ) => {
return ( value && value.length > 0 ) || false;
},
Edit: SlugEdit,
render: SlugView,
};
Expand Down
34 changes: 26 additions & 8 deletions packages/fields/src/fields/slug/slug-edit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,21 @@ import {
import { copySmall } from '@wordpress/icons';
import { useCopyToClipboard, useInstanceId } from '@wordpress/compose';
import { useDispatch } from '@wordpress/data';
import { useCallback, useEffect, useRef } from '@wordpress/element';
import { useCallback, useEffect, useRef, useState } 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';

/**
* External dependencies
*/
import clsx from 'clsx';

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

const SlugEdit = ( {
field,
Expand All @@ -30,7 +34,12 @@ const SlugEdit = ( {
}: DataFormControlProps< BasePost > ) => {
const { id } = field;

const slug = field.getValue( { item: data } ) || getSlug( data );
const value = field.getValue( { item: data } );

const slug = value.length > 0 ? value : data.id.toString();

const [ isValid, setIsValid ] = useState( true );

const permalinkTemplate = data.permalink_template || '';
const PERMALINK_POSTNAME_REGEX = /%(?:postname|pagename)%/;
const [ prefix, suffix ] = permalinkTemplate.split(
Expand All @@ -40,7 +49,7 @@ const SlugEdit = ( {
const permalinkSuffix = suffix;
const isEditable = PERMALINK_POSTNAME_REGEX.test( permalinkTemplate );
const originalSlugRef = useRef( slug );
const slugToDisplay = slug || originalSlugRef.current;
const slugToDisplay = slug.length > 0 ? slug : data.id.toString();
const permalink = isEditable
? `${ permalinkPrefix }${ slugToDisplay }${ permalinkSuffix }`
: safeDecodeURIComponent( data.link || '' );
Expand All @@ -52,11 +61,13 @@ const SlugEdit = ( {
}, [ slug ] );

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

const { createNotice } = useDispatch( noticesStore );
Expand Down Expand Up @@ -106,7 +117,9 @@ const SlugEdit = ( {
autoComplete="off"
spellCheck="false"
type="text"
className="fields-controls__slug-input"
className={ clsx( 'fields-controls__slug-input', {
'fields-controls__slug-input--invalid': ! isValid,
} ) }
onChange={ ( newValue?: string ) => {
onChangeControl( newValue );
} }
Expand All @@ -117,6 +130,11 @@ const SlugEdit = ( {
} }
aria-describedby={ postUrlSlugDescriptionId }
/>
{ ! isValid && (
<div className="fields-controls__slug-error">
<span>{ __( 'The slug is invalid.' ) }</span>
</div>
) }
<div className="fields-controls__slug-help">
<span className="fields-controls__slug-help-visual-label">
{ __( 'Permalink:' ) }
Expand Down
11 changes: 10 additions & 1 deletion packages/fields/src/fields/slug/slug-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,17 @@ import { useEffect, useRef } from '@wordpress/element';
import type { BasePost } from '../../types';
import { getSlug } from './utils';

const getSlugOrFallback = ( item: BasePost ): string => {
if ( typeof item === 'object' ) {
const slug = getSlug( item );
return slug.length > 0 ? slug : item.id.toString();
}

return '';
};

const SlugView = ( { item }: { item: BasePost } ) => {
const slug = typeof item === 'object' ? getSlug( item ) : '';
const slug = getSlugOrFallback( item );
const originalSlugRef = useRef( slug );

useEffect( () => {
Expand Down
12 changes: 12 additions & 0 deletions packages/fields/src/fields/slug/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,18 @@
padding-inline-start: 0 !important;
}

.fields-controls__slug-input--invalid {
.components-input-control__backdrop,
.components-input-control__backdrop:focus-within {
border-color: $alert-red !important;
box-shadow: 0 0 0 0.5px $alert-red !important;
}
}

.fields-controls__slug-error {
color: $alert-red;
}

.fields-controls__slug-help-link {
word-break: break-word;
}
Expand Down
4 changes: 1 addition & 3 deletions packages/fields/src/fields/slug/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,5 @@ import type { BasePost } from '../../types';
import { getItemTitle } from '../../actions/utils';

export const getSlug = ( item: BasePost ): string => {
return (
item.slug || cleanForSlug( getItemTitle( item ) ) || item.id.toString()
);
return item.slug || cleanForSlug( getItemTitle( item ) );
};
Loading