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

Adds ui placeholders for selecting publishing principles #20349

Merged
merged 24 commits into from
Jun 20, 2023
Merged
Show file tree
Hide file tree
Changes from 23 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
7 changes: 7 additions & 0 deletions admin/tracking/class-tracking-settings-data.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,13 @@ class WPSEO_Tracking_Settings_Data implements WPSEO_Collection {
'most_linked_ignore_list',
'least_linked_ignore_list',
'indexables_page_reading_list',
'publishing_principles_id',
'ownership_funding_info_id',
'actionable_feedback_policy_id',
'corrections_policy_id',
'ethics_policy_id',
'diversity_policy_id',
'diversity_staffing_report_id',
];

/**
Expand Down
16 changes: 15 additions & 1 deletion inc/options/class-wpseo-option-titles.php
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,14 @@ class WPSEO_Option_Titles extends WPSEO_Option {
'open_graph_frontpage_image' => '', // Text field.
'open_graph_frontpage_image_id' => 0,

'publishing_principles_id' => false,
thijsoo marked this conversation as resolved.
Show resolved Hide resolved
'ownership_funding_info_id' => false,
'actionable_feedback_policy_id' => false,
'corrections_policy_id' => false,
'ethics_policy_id' => false,
'diversity_policy_id' => false,
'diversity_staffing_report_id' => false,

/*
* Uses enrich_defaults to add more along the lines of:
* - 'title-' . $pt->name => ''; // Text field.
Expand Down Expand Up @@ -579,6 +587,13 @@ protected function validate_option( $dirty, $clean, $old ) {
case 'person_logo_id':
case 'social-image-id-':
case 'open_graph_frontpage_image_id':
case 'publishing_principles_id':
case 'ownership_funding_info_id':
case 'actionable_feedback_policy_id':
case 'corrections_policy_id':
case 'ethics_policy_id':
case 'diversity_policy_id':
case 'diversity_staffing_report_id':
if ( isset( $dirty[ $key ] ) ) {
$int = WPSEO_Utils::validate_int( $dirty[ $key ] );
if ( $int !== false && $int >= 0 ) {
Expand All @@ -592,7 +607,6 @@ protected function validate_option( $dirty, $clean, $old ) {
}
}
break;

/* Separator field - Radio. */
case 'separator':
if ( isset( $dirty[ $key ] ) && $dirty[ $key ] !== '' ) {
Expand Down
148 changes: 148 additions & 0 deletions packages/js/src/settings/components/formik-page-select-field.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
/* eslint-disable complexity */
import { DocumentAddIcon } from "@heroicons/react/outline";
import { useCallback, useMemo, useState } from "@wordpress/element";
import { __ } from "@wordpress/i18n";
import { AutocompleteField, Spinner } from "@yoast/ui-library";
import classNames from "classnames";
import { useField } from "formik";
import { debounce, find, isEmpty, map, values } from "lodash";
import PropTypes from "prop-types";
import { ASYNC_ACTION_STATUS } from "../constants";
import { useDispatchSettings, useSelectSettings } from "../hooks";

/**
* @param {JSX.node} children The children.
* @param {string} [className] The className.
* @returns {JSX.Element} The page select options content decorator component.
*/
const PageSelectOptionsContent = ( { children, className = "" } ) => (
<div className={ classNames( "yst-flex yst-items-center yst-justify-center yst-gap-2 yst-py-2 yst-px-3", className ) }>
{ children }
</div>
);

PageSelectOptionsContent.propTypes = {
children: PropTypes.node.isRequired,
className: PropTypes.string,
};

/**
* @param {Object} props The props object.
* @param {string} props.name The field name.
* @param {string} props.id The field id.
* @param {string} props.className The className.
* @returns {JSX.Element} The page select component.
*/
const FormikPageSelectField = ( { name, id, className = "", ...props } ) => {
const siteBasicsPolicies = useSelectSettings( "selectPreference", [], "siteBasicsPolicies", {} );
const pages = useSelectSettings( "selectPagesWith", [ siteBasicsPolicies ], values( siteBasicsPolicies ) );
const { fetchPages } = useDispatchSettings();
const [ { value, ...field }, , { setTouched, setValue } ] = useField( { type: "select", name, id, ...props } );
const [ status, setStatus ] = useState( ASYNC_ACTION_STATUS.idle );
const [ queriedPageIds, setQueriedPageIds ] = useState( [] );
thijsoo marked this conversation as resolved.
Show resolved Hide resolved
const canCreatePages = useSelectSettings( "selectPreference", [], "canCreatePages", false );
const createPageUrl = useSelectSettings( "selectPreference", [], "createPageUrl", "" );

const selectedPage = useMemo( () => {
const pageObjects = values( pages );
return find( pageObjects, [ "id", value ] );
}, [ value, pages ] );

const debouncedFetchPages = useCallback( debounce( async search => {
try {
setStatus( ASYNC_ACTION_STATUS.loading );
// eslint-disable-next-line camelcase
const response = await fetchPages( { search } );

setQueriedPageIds( map( response.payload, "id" ) );
setStatus( ASYNC_ACTION_STATUS.success );
} catch ( error ) {
if ( error instanceof DOMException && error.name === "AbortError" ) {
// Expected abort errors can be ignored.
return;
}
setQueriedPageIds( [] );
setStatus( ASYNC_ACTION_STATUS.error );
}
}, 200 ), [ setQueriedPageIds, setStatus, fetchPages ] );

const handleChange = useCallback( newValue => {
setTouched( true, false );
setValue( newValue );
}, [ setValue, setTouched ] );
const handleQueryChange = useCallback( event => debouncedFetchPages( event.target.value ), [ debouncedFetchPages ] );
const selectablePages = useMemo( () => isEmpty( queriedPageIds ) ? map( pages, "id" ) : queriedPageIds, [ queriedPageIds, pages ] );
const hasNoPages = useMemo( () => ( status === ASYNC_ACTION_STATUS.success && isEmpty( queriedPageIds ) ), [ queriedPageIds, status ] );

return (
<AutocompleteField
{ ...field }
name={ name }
id={ id }
// Hack to force re-render of Headless UI Combobox.Input component when selectedPage changes.
value={ selectedPage ? value : 0 }
onChange={ handleChange }
placeholder={ __( "Select a page...", "wordpress-seo" ) }
selectedLabel={ selectedPage?.name }
onQueryChange={ handleQueryChange }
className={ className && props.disabled && "yst-autocomplete--disabled" }
nullable={ true }
{ ...props }
>
<>
{ ( status === ASYNC_ACTION_STATUS.idle || status === ASYNC_ACTION_STATUS.success ) && (
<>
{ hasNoPages ? (
<PageSelectOptionsContent>
{ __( "No pages found.", "wordpress-seo" ) }
</PageSelectOptionsContent>
) : map( selectablePages, pageId => {
const page = pages?.[ pageId ];
return page ? (
<AutocompleteField.Option key={ page?.id } value={ page?.id }>
{ page?.name }
</AutocompleteField.Option>
) : null;
} ) }
{ canCreatePages && (
<li className="yst-sticky yst-inset-x-0 yst-bottom-0 yst-group">
<a
id={ `link-create_page-${ id }` }
href={ createPageUrl }
target="_blank"
rel="noreferrer"
className="yst-relative yst-w-full yst-flex yst-items-center yst-py-4 yst-px-3 yst-gap-2 yst-no-underline yst-text-sm yst-text-left yst-bg-white yst-text-slate-700 group-hover:yst-text-white group-hover:yst-bg-primary-500 yst-border-t yst-border-slate-200"
>
<DocumentAddIcon
className="yst-w-5 yst-h-5 yst-text-slate-400 group-hover:yst-text-white"
/>
<span>{ __( "Add new page...", "wordpress-seo" ) }</span>
</a>
</li>
) }
</>
) }
{ status === ASYNC_ACTION_STATUS.loading && (
<PageSelectOptionsContent>
<Spinner variant="primary" />
{ __( "Searching pages...", "wordpress-seo" ) }
</PageSelectOptionsContent>
) }
{ status === ASYNC_ACTION_STATUS.error && (
<PageSelectOptionsContent className="yst-text-red-600">
{ __( "Failed to retrieve pages.", "wordpress-seo" ) }
</PageSelectOptionsContent>
) }
</>
</AutocompleteField>
);
};

FormikPageSelectField.propTypes = {
name: PropTypes.string.isRequired,
id: PropTypes.string.isRequired,
className: PropTypes.string,
disabled: PropTypes.bool,
thijsoo marked this conversation as resolved.
Show resolved Hide resolved
};

export default FormikPageSelectField;
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ const FormikUserSelectField = ( { name, id, className = "", ...props } ) => {
{ ...props }
>
<>
{ status === ASYNC_ACTION_STATUS.idle || status === ASYNC_ACTION_STATUS.success && (
{ ( status === ASYNC_ACTION_STATUS.idle || status === ASYNC_ACTION_STATUS.success ) && (
thijsoo marked this conversation as resolved.
Show resolved Hide resolved
<>
{ isEmpty( queriedUserIds ) ? (
<UserSelectOptionsContent>
Expand Down
1 change: 1 addition & 0 deletions packages/js/src/settings/components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export { default as FormikMediaSelectField } from "./formik-media-select-field";
export { default as FormikReplacementVariableEditorField } from "./formik-replacement-variable-editor-field";
export { default as FormikTagField } from "./formik-tag-field";
export { default as FormikUserSelectField } from "./formik-user-select-field";
export { default as FormikPageSelectField } from "./formik-page-select-field";
export { default as FormikValueChangeField } from "./formik-value-change-field";
export { default as FormikWithErrorField } from "./formik-with-error-field";
export { default as Notifications } from "./notifications";
Expand Down
64 changes: 64 additions & 0 deletions packages/js/src/settings/helpers/search.js
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,70 @@ export const createSearchIndex = ( postTypes, taxonomies, { userLocale } = {} )
fieldLabel: __( "Usage tracking", "wordpress-seo" ),
keywords: [],
},
publishing_principles_id: {
route: "/site-basics",
routeLabel: __( "Site basics", "wordpress-seo" ),
fieldId: "input-wpseo_titles-publishing_principles_id",
fieldLabel: __( "Publishing principles", "wordpress-seo" ),
keywords: [
__( "Publishing policies", "wordpress-seo" ),
],
},
ownership_funding_info_id: {
route: "/site-basics",
routeLabel: __( "Site basics", "wordpress-seo" ),
fieldId: "input-wpseo_titles-ownership_funding_info_id",
fieldLabel: __( "Ownership / Funding info", "wordpress-seo" ),
keywords: [
__( "Publishing policies", "wordpress-seo" ),

],
},
actionable_feedback_policy_id: {
route: "/site-basics",
routeLabel: __( "Site basics", "wordpress-seo" ),
fieldId: "input-wpseo_titles-actionable_feedback_policy_id",
fieldLabel: __( "Actionable feedback policy", "wordpress-seo" ),
keywords: [
__( "Publishing policies", "wordpress-seo" ),
],
},
corrections_policy_id: {
route: "/site-basics",
routeLabel: __( "Site basics", "wordpress-seo" ),
fieldId: "input-wpseo_titles-corrections_policy_id",
fieldLabel: __( "Corrections policy", "wordpress-seo" ),
keywords: [
__( "Publishing policies", "wordpress-seo" ),
],
},
ethics_policy_id: {
route: "/site-basics",
routeLabel: __( "Site basics", "wordpress-seo" ),
fieldId: "input-wpseo_titles-ethics_policy_id",
fieldLabel: __( "Ethics policy", "wordpress-seo" ),
keywords: [
__( "Publishing policies", "wordpress-seo" ),
],
},
diversity_policy_id: {
route: "/site-basics",
routeLabel: __( "Site basics", "wordpress-seo" ),
fieldId: "input-wpseo_titles-diversity_policy_id",
fieldLabel: __( "Diversity policy", "wordpress-seo" ),
keywords: [
__( "Publishing policies", "wordpress-seo" ),
],
},
diversity_staffing_report_id: {
route: "/site-basics",
routeLabel: __( "Site basics", "wordpress-seo" ),
fieldId: "input-wpseo_titles-diversity_staffing_report_id",
fieldLabel: __( "Diversity staffing report", "wordpress-seo" ),
keywords: [
__( "Publishing policies", "wordpress-seo" ),
],
},
baiduverify: {
route: "/site-connections",
routeLabel: __( "Site connections", "wordpress-seo" ),
Expand Down
Loading