@wordpress/fields 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2 -0
- package/README.md +12 -0
- package/build/actions/delete-post.js +4 -4
- package/build/actions/delete-post.js.map +1 -1
- package/build/actions/duplicate-post.js +2 -2
- package/build/actions/duplicate-post.js.map +1 -1
- package/build/actions/view-post-revisions.js +1 -1
- package/build/actions/view-post-revisions.js.map +1 -1
- package/build/fields/featured-image/featured-image-edit.js +113 -0
- package/build/fields/featured-image/featured-image-edit.js.map +1 -0
- package/build/fields/featured-image/featured-image-view.js +41 -0
- package/build/fields/featured-image/featured-image-view.js.map +1 -0
- package/build/fields/featured-image/index.js +30 -0
- package/build/fields/featured-image/index.js.map +1 -0
- package/build/fields/index.js +21 -0
- package/build/fields/index.js.map +1 -1
- package/build/fields/parent/index.js +34 -0
- package/build/fields/parent/index.js.map +1 -0
- package/build/fields/parent/parent-edit.js +243 -0
- package/build/fields/parent/parent-edit.js.map +1 -0
- package/build/fields/parent/parent-view.js +39 -0
- package/build/fields/parent/parent-view.js.map +1 -0
- package/build/fields/parent/utils.js +20 -0
- package/build/fields/parent/utils.js.map +1 -0
- package/build/fields/slug/index.js +30 -0
- package/build/fields/slug/index.js.map +1 -0
- package/build/fields/slug/slug-edit.js +132 -0
- package/build/fields/slug/slug-edit.js.map +1 -0
- package/build/fields/slug/slug-view.js +30 -0
- package/build/fields/slug/slug-view.js.map +1 -0
- package/build/mutation/index.js +2 -2
- package/build/mutation/index.js.map +1 -1
- package/build/types.js.map +1 -1
- package/build-module/actions/delete-post.js +5 -5
- package/build-module/actions/delete-post.js.map +1 -1
- package/build-module/actions/duplicate-post.js +2 -2
- package/build-module/actions/duplicate-post.js.map +1 -1
- package/build-module/actions/view-post-revisions.js +1 -1
- package/build-module/actions/view-post-revisions.js.map +1 -1
- package/build-module/fields/featured-image/featured-image-edit.js +105 -0
- package/build-module/fields/featured-image/featured-image-edit.js.map +1 -0
- package/build-module/fields/featured-image/featured-image-view.js +33 -0
- package/build-module/fields/featured-image/featured-image-view.js.map +1 -0
- package/build-module/fields/featured-image/index.js +24 -0
- package/build-module/fields/featured-image/index.js.map +1 -0
- package/build-module/fields/index.js +3 -0
- package/build-module/fields/index.js.map +1 -1
- package/build-module/fields/parent/index.js +28 -0
- package/build-module/fields/parent/index.js.map +1 -0
- package/build-module/fields/parent/parent-edit.js +230 -0
- package/build-module/fields/parent/parent-edit.js.map +1 -0
- package/build-module/fields/parent/parent-view.js +32 -0
- package/build-module/fields/parent/parent-view.js.map +1 -0
- package/build-module/fields/parent/utils.js +14 -0
- package/build-module/fields/parent/utils.js.map +1 -0
- package/build-module/fields/slug/index.js +23 -0
- package/build-module/fields/slug/index.js.map +1 -0
- package/build-module/fields/slug/slug-edit.js +125 -0
- package/build-module/fields/slug/slug-edit.js.map +1 -0
- package/build-module/fields/slug/slug-view.js +24 -0
- package/build-module/fields/slug/slug-view.js.map +1 -0
- package/build-module/mutation/index.js +2 -2
- package/build-module/mutation/index.js.map +1 -1
- package/build-module/types.js.map +1 -1
- package/build-style/styles-rtl.css +134 -0
- package/build-style/styles.css +134 -0
- package/build-types/actions/delete-post.d.ts.map +1 -1
- package/build-types/fields/featured-image/featured-image-edit.d.ts +7 -0
- package/build-types/fields/featured-image/featured-image-edit.d.ts.map +1 -0
- package/build-types/fields/featured-image/featured-image-view.d.ts +7 -0
- package/build-types/fields/featured-image/featured-image-view.d.ts.map +1 -0
- package/build-types/fields/featured-image/index.d.ts +11 -0
- package/build-types/fields/featured-image/index.d.ts.map +1 -0
- package/build-types/fields/index.d.ts +3 -0
- package/build-types/fields/index.d.ts.map +1 -1
- package/build-types/fields/parent/index.d.ts +14 -0
- package/build-types/fields/parent/index.d.ts.map +1 -0
- package/build-types/fields/parent/parent-edit.d.ts +9 -0
- package/build-types/fields/parent/parent-edit.d.ts.map +1 -0
- package/build-types/fields/parent/parent-view.d.ts +7 -0
- package/build-types/fields/parent/parent-view.d.ts.map +1 -0
- package/build-types/fields/parent/utils.d.ts +6 -0
- package/build-types/fields/parent/utils.d.ts.map +1 -0
- package/build-types/fields/slug/index.d.ts +11 -0
- package/build-types/fields/slug/index.d.ts.map +1 -0
- package/build-types/fields/slug/slug-edit.d.ts +8 -0
- package/build-types/fields/slug/slug-edit.d.ts.map +1 -0
- package/build-types/fields/slug/slug-view.d.ts +9 -0
- package/build-types/fields/slug/slug-view.d.ts.map +1 -0
- package/build-types/types.d.ts +2 -0
- package/build-types/types.d.ts.map +1 -1
- package/package.json +26 -22
- package/src/actions/delete-post.tsx +8 -5
- package/src/actions/duplicate-post.tsx +2 -2
- package/src/actions/view-post-revisions.tsx +1 -1
- package/src/fields/featured-image/featured-image-edit.tsx +122 -0
- package/src/fields/featured-image/featured-image-view.tsx +38 -0
- package/src/fields/featured-image/index.ts +24 -0
- package/src/fields/featured-image/style.scss +95 -0
- package/src/fields/index.ts +3 -0
- package/src/fields/parent/index.ts +27 -0
- package/src/fields/parent/parent-edit.tsx +348 -0
- package/src/fields/parent/parent-view.tsx +33 -0
- package/src/fields/parent/utils.ts +18 -0
- package/src/fields/slug/index.ts +23 -0
- package/src/fields/slug/slug-edit.tsx +156 -0
- package/src/fields/slug/slug-view.tsx +26 -0
- package/src/fields/slug/style.scss +22 -0
- package/src/mutation/index.ts +3 -3
- package/src/styles.scss +1 -0
- package/src/types.ts +2 -0
- package/tsconfig.json +3 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WordPress dependencies
|
|
3
|
+
*/
|
|
4
|
+
import { Button, __experimentalGrid as Grid } from '@wordpress/components';
|
|
5
|
+
import { useSelect } from '@wordpress/data';
|
|
6
|
+
import { useCallback, useRef } from '@wordpress/element';
|
|
7
|
+
// @ts-ignore
|
|
8
|
+
import { MediaUpload } from '@wordpress/media-utils';
|
|
9
|
+
import { lineSolid } from '@wordpress/icons';
|
|
10
|
+
import { store as coreStore } from '@wordpress/core-data';
|
|
11
|
+
import type { DataFormControlProps } from '@wordpress/dataviews';
|
|
12
|
+
/**
|
|
13
|
+
* Internal dependencies
|
|
14
|
+
*/
|
|
15
|
+
import type { BasePost } from '../../types';
|
|
16
|
+
import { __ } from '@wordpress/i18n';
|
|
17
|
+
|
|
18
|
+
export const FeaturedImageEdit = ( {
|
|
19
|
+
data,
|
|
20
|
+
field,
|
|
21
|
+
onChange,
|
|
22
|
+
}: DataFormControlProps< BasePost > ) => {
|
|
23
|
+
const { id } = field;
|
|
24
|
+
|
|
25
|
+
const value = field.getValue( { item: data } );
|
|
26
|
+
|
|
27
|
+
const media = useSelect(
|
|
28
|
+
( select ) => {
|
|
29
|
+
const { getEntityRecord } = select( coreStore );
|
|
30
|
+
return getEntityRecord( 'root', 'media', value );
|
|
31
|
+
},
|
|
32
|
+
[ value ]
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
const onChangeControl = useCallback(
|
|
36
|
+
( newValue: number ) =>
|
|
37
|
+
onChange( {
|
|
38
|
+
[ id ]: newValue,
|
|
39
|
+
} ),
|
|
40
|
+
[ id, onChange ]
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
const url = media?.source_url;
|
|
44
|
+
const title = media?.title?.rendered;
|
|
45
|
+
const ref = useRef( null );
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<fieldset className="fields-controls__featured-image">
|
|
49
|
+
<div className="fields-controls__featured-image-container">
|
|
50
|
+
<MediaUpload
|
|
51
|
+
onSelect={ ( selectedMedia: { id: number } ) => {
|
|
52
|
+
onChangeControl( selectedMedia.id );
|
|
53
|
+
} }
|
|
54
|
+
allowedTypes={ [ 'image' ] }
|
|
55
|
+
render={ ( { open }: { open: () => void } ) => {
|
|
56
|
+
return (
|
|
57
|
+
<div
|
|
58
|
+
ref={ ref }
|
|
59
|
+
role="button"
|
|
60
|
+
tabIndex={ -1 }
|
|
61
|
+
onClick={ () => {
|
|
62
|
+
open();
|
|
63
|
+
} }
|
|
64
|
+
onKeyDown={ open }
|
|
65
|
+
>
|
|
66
|
+
<Grid
|
|
67
|
+
rowGap={ 0 }
|
|
68
|
+
columnGap={ 8 }
|
|
69
|
+
templateColumns="24px 1fr 24px"
|
|
70
|
+
>
|
|
71
|
+
{ url && (
|
|
72
|
+
<>
|
|
73
|
+
<img
|
|
74
|
+
className="fields-controls__featured-image-image"
|
|
75
|
+
alt=""
|
|
76
|
+
width={ 24 }
|
|
77
|
+
height={ 24 }
|
|
78
|
+
src={ url }
|
|
79
|
+
/>
|
|
80
|
+
<span className="fields-controls__featured-image-title">
|
|
81
|
+
{ title }
|
|
82
|
+
</span>
|
|
83
|
+
</>
|
|
84
|
+
) }
|
|
85
|
+
{ ! url && (
|
|
86
|
+
<>
|
|
87
|
+
<span
|
|
88
|
+
className="fields-controls__featured-image-placeholder"
|
|
89
|
+
style={ {
|
|
90
|
+
width: '24px',
|
|
91
|
+
height: '24px',
|
|
92
|
+
} }
|
|
93
|
+
/>
|
|
94
|
+
<span className="fields-controls__featured-image-title">
|
|
95
|
+
{ __( 'Choose an image…' ) }
|
|
96
|
+
</span>
|
|
97
|
+
</>
|
|
98
|
+
) }
|
|
99
|
+
{ url && (
|
|
100
|
+
<>
|
|
101
|
+
<Button
|
|
102
|
+
size="small"
|
|
103
|
+
className="fields-controls__featured-image-remove-button"
|
|
104
|
+
icon={ lineSolid }
|
|
105
|
+
onClick={ (
|
|
106
|
+
event: React.MouseEvent< HTMLButtonElement >
|
|
107
|
+
) => {
|
|
108
|
+
event.stopPropagation();
|
|
109
|
+
onChangeControl( 0 );
|
|
110
|
+
} }
|
|
111
|
+
/>
|
|
112
|
+
</>
|
|
113
|
+
) }
|
|
114
|
+
</Grid>
|
|
115
|
+
</div>
|
|
116
|
+
);
|
|
117
|
+
} }
|
|
118
|
+
/>
|
|
119
|
+
</div>
|
|
120
|
+
</fieldset>
|
|
121
|
+
);
|
|
122
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WordPress dependencies
|
|
3
|
+
*/
|
|
4
|
+
import { useSelect } from '@wordpress/data';
|
|
5
|
+
import { store as coreStore } from '@wordpress/core-data';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Internal dependencies
|
|
9
|
+
*/
|
|
10
|
+
import type { BasePost } from '../../types';
|
|
11
|
+
import type { DataViewRenderFieldProps } from '@wordpress/dataviews';
|
|
12
|
+
|
|
13
|
+
export const FeaturedImageView = ( {
|
|
14
|
+
item,
|
|
15
|
+
}: DataViewRenderFieldProps< BasePost > ) => {
|
|
16
|
+
const mediaId = item.featured_media;
|
|
17
|
+
|
|
18
|
+
const media = useSelect(
|
|
19
|
+
( select ) => {
|
|
20
|
+
const { getEntityRecord } = select( coreStore );
|
|
21
|
+
return mediaId ? getEntityRecord( 'root', 'media', mediaId ) : null;
|
|
22
|
+
},
|
|
23
|
+
[ mediaId ]
|
|
24
|
+
);
|
|
25
|
+
const url = media?.source_url;
|
|
26
|
+
|
|
27
|
+
if ( url ) {
|
|
28
|
+
return (
|
|
29
|
+
<img
|
|
30
|
+
className="fields-controls__featured-image-image"
|
|
31
|
+
src={ url }
|
|
32
|
+
alt=""
|
|
33
|
+
/>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return <span className="fields-controls__featured-image-placeholder" />;
|
|
38
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WordPress dependencies
|
|
3
|
+
*/
|
|
4
|
+
import type { Field } from '@wordpress/dataviews';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Internal dependencies
|
|
8
|
+
*/
|
|
9
|
+
import type { BasePost } from '../../types';
|
|
10
|
+
import { __ } from '@wordpress/i18n';
|
|
11
|
+
import { FeaturedImageEdit } from './featured-image-edit';
|
|
12
|
+
import { FeaturedImageView } from './featured-image-view';
|
|
13
|
+
|
|
14
|
+
const featuredImageField: Field< BasePost > = {
|
|
15
|
+
id: 'featured_media',
|
|
16
|
+
type: 'text',
|
|
17
|
+
label: __( 'Featured Image' ),
|
|
18
|
+
getValue: ( { item } ) => item.featured_media,
|
|
19
|
+
Edit: FeaturedImageEdit,
|
|
20
|
+
render: FeaturedImageView,
|
|
21
|
+
enableSorting: false,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export default featuredImageField;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
.fields-controls__featured-image-placeholder {
|
|
2
|
+
border-radius: $radius-small;
|
|
3
|
+
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.2);
|
|
4
|
+
display: inline-block;
|
|
5
|
+
padding: 0;
|
|
6
|
+
background:
|
|
7
|
+
$white
|
|
8
|
+
linear-gradient(-45deg, transparent 48%, $gray-300 48%, $gray-300 52%, transparent 52%);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.fields-controls__featured-image-title {
|
|
12
|
+
width: 100%;
|
|
13
|
+
color: $gray-900;
|
|
14
|
+
white-space: nowrap;
|
|
15
|
+
text-overflow: ellipsis;
|
|
16
|
+
overflow: hidden;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.fields-controls__featured-image-image {
|
|
20
|
+
width: 100%;
|
|
21
|
+
height: 100%;
|
|
22
|
+
border-radius: $radius-small;
|
|
23
|
+
align-self: center;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.fields-controls__featured-image-container {
|
|
27
|
+
.fields-controls__featured-image-placeholder {
|
|
28
|
+
margin: 0;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
span {
|
|
32
|
+
margin-right: auto;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
fieldset.fields-controls__featured-image {
|
|
37
|
+
.fields-controls__featured-image-container {
|
|
38
|
+
border: $border-width solid $gray-300;
|
|
39
|
+
border-radius: $radius-small;
|
|
40
|
+
padding: 8px 12px;
|
|
41
|
+
cursor: pointer;
|
|
42
|
+
&:hover {
|
|
43
|
+
background-color: $gray-100;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.fields-controls__featured-image-placeholder {
|
|
48
|
+
width: 24px;
|
|
49
|
+
height: 24px;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
span {
|
|
53
|
+
align-self: center;
|
|
54
|
+
text-align: start;
|
|
55
|
+
white-space: nowrap;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.fields-controls__featured-image-upload-button {
|
|
59
|
+
padding: 0;
|
|
60
|
+
height: fit-content;
|
|
61
|
+
&:hover,
|
|
62
|
+
&:focus {
|
|
63
|
+
border: 0;
|
|
64
|
+
color: unset;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.fields-controls__featured-image-remove-button {
|
|
69
|
+
place-self: end;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.dataforms-layouts-panel__field-control {
|
|
74
|
+
.fields-controls__featured-image-image {
|
|
75
|
+
width: 16px;
|
|
76
|
+
height: 16px;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.fields-controls__featured-image-placeholder {
|
|
80
|
+
width: 16px;
|
|
81
|
+
height: 16px;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.dataviews-view-table__cell-content-wrapper {
|
|
86
|
+
.fields-controls__featured-image-image {
|
|
87
|
+
width: 32px;
|
|
88
|
+
height: 32px;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.fields-controls__featured-image-placeholder {
|
|
92
|
+
width: 32px;
|
|
93
|
+
height: 32px;
|
|
94
|
+
}
|
|
95
|
+
}
|
package/src/fields/index.ts
CHANGED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WordPress dependencies
|
|
3
|
+
*/
|
|
4
|
+
import type { Field } from '@wordpress/dataviews';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Internal dependencies
|
|
8
|
+
*/
|
|
9
|
+
import type { BasePost } from '../../types';
|
|
10
|
+
import { __ } from '@wordpress/i18n';
|
|
11
|
+
import { ParentEdit } from './parent-edit';
|
|
12
|
+
import { ParentView } from './parent-view';
|
|
13
|
+
|
|
14
|
+
const parentField: Field< BasePost > = {
|
|
15
|
+
id: 'parent',
|
|
16
|
+
type: 'text',
|
|
17
|
+
label: __( 'Parent' ),
|
|
18
|
+
getValue: ( { item } ) => item.parent,
|
|
19
|
+
Edit: ParentEdit,
|
|
20
|
+
render: ParentView,
|
|
21
|
+
enableSorting: true,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* This field is used to display the post parent.
|
|
26
|
+
*/
|
|
27
|
+
export default parentField;
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WordPress dependencies
|
|
3
|
+
*/
|
|
4
|
+
import { ComboboxControl, ExternalLink } from '@wordpress/components';
|
|
5
|
+
import { useSelect } from '@wordpress/data';
|
|
6
|
+
import {
|
|
7
|
+
createInterpolateElement,
|
|
8
|
+
useCallback,
|
|
9
|
+
useMemo,
|
|
10
|
+
useState,
|
|
11
|
+
} from '@wordpress/element';
|
|
12
|
+
// @ts-ignore
|
|
13
|
+
import { store as coreStore } from '@wordpress/core-data';
|
|
14
|
+
import type { DataFormControlProps } from '@wordpress/dataviews';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* External dependencies
|
|
18
|
+
*/
|
|
19
|
+
import removeAccents from 'remove-accents';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Internal dependencies
|
|
23
|
+
*/
|
|
24
|
+
import { debounce } from '@wordpress/compose';
|
|
25
|
+
import { decodeEntities } from '@wordpress/html-entities';
|
|
26
|
+
import { __, sprintf } from '@wordpress/i18n';
|
|
27
|
+
import type { BasePost } from '../../types';
|
|
28
|
+
import { getTitleWithFallbackName } from './utils';
|
|
29
|
+
import { filterURLForDisplay } from '@wordpress/url';
|
|
30
|
+
|
|
31
|
+
type TreeBase = {
|
|
32
|
+
id: number;
|
|
33
|
+
name: string;
|
|
34
|
+
[ key: string ]: any;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
type TreeWithParent = TreeBase & {
|
|
38
|
+
parent: number;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
type TreeWithoutParent = TreeBase & {
|
|
42
|
+
parent: null;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
type Tree = TreeWithParent | TreeWithoutParent;
|
|
46
|
+
|
|
47
|
+
function buildTermsTree( flatTerms: Tree[] ) {
|
|
48
|
+
const flatTermsWithParentAndChildren = flatTerms.map( ( term ) => {
|
|
49
|
+
return {
|
|
50
|
+
children: [],
|
|
51
|
+
...term,
|
|
52
|
+
};
|
|
53
|
+
} );
|
|
54
|
+
|
|
55
|
+
// All terms should have a `parent` because we're about to index them by it.
|
|
56
|
+
if (
|
|
57
|
+
flatTermsWithParentAndChildren.some(
|
|
58
|
+
( { parent } ) => parent === null || parent === undefined
|
|
59
|
+
)
|
|
60
|
+
) {
|
|
61
|
+
return flatTermsWithParentAndChildren as TreeWithParent[];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const termsByParent = (
|
|
65
|
+
flatTermsWithParentAndChildren as TreeWithParent[]
|
|
66
|
+
).reduce(
|
|
67
|
+
( acc, term ) => {
|
|
68
|
+
const { parent } = term;
|
|
69
|
+
if ( ! acc[ parent ] ) {
|
|
70
|
+
acc[ parent ] = [];
|
|
71
|
+
}
|
|
72
|
+
acc[ parent ].push( term );
|
|
73
|
+
return acc;
|
|
74
|
+
},
|
|
75
|
+
{} as Record< string, Array< TreeWithParent > >
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
const fillWithChildren = (
|
|
79
|
+
terms: Array< TreeWithParent >
|
|
80
|
+
): Array< TreeWithParent > => {
|
|
81
|
+
return terms.map( ( term ) => {
|
|
82
|
+
const children = termsByParent[ term.id ];
|
|
83
|
+
return {
|
|
84
|
+
...term,
|
|
85
|
+
children:
|
|
86
|
+
children && children.length
|
|
87
|
+
? fillWithChildren( children )
|
|
88
|
+
: [],
|
|
89
|
+
};
|
|
90
|
+
} );
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
return fillWithChildren( termsByParent[ '0' ] || [] );
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export const getItemPriority = ( name: string, searchValue: string ) => {
|
|
97
|
+
const normalizedName = removeAccents( name || '' ).toLowerCase();
|
|
98
|
+
const normalizedSearch = removeAccents( searchValue || '' ).toLowerCase();
|
|
99
|
+
if ( normalizedName === normalizedSearch ) {
|
|
100
|
+
return 0;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if ( normalizedName.startsWith( normalizedSearch ) ) {
|
|
104
|
+
return normalizedName.length;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return Infinity;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
export function PageAttributesParent( {
|
|
111
|
+
data,
|
|
112
|
+
onChangeControl,
|
|
113
|
+
}: {
|
|
114
|
+
data: BasePost;
|
|
115
|
+
onChangeControl: ( newValue: number ) => void;
|
|
116
|
+
} ) {
|
|
117
|
+
const [ fieldValue, setFieldValue ] = useState< null | string >( null );
|
|
118
|
+
|
|
119
|
+
const pageId = data.parent;
|
|
120
|
+
const postId = data.id;
|
|
121
|
+
const postTypeSlug = data.type;
|
|
122
|
+
|
|
123
|
+
const { parentPostTitle, pageItems, isHierarchical } = useSelect(
|
|
124
|
+
( select ) => {
|
|
125
|
+
// @ts-expect-error getPostType is not typed
|
|
126
|
+
const { getEntityRecord, getEntityRecords, getPostType } =
|
|
127
|
+
select( coreStore );
|
|
128
|
+
|
|
129
|
+
const postTypeInfo = getPostType( postTypeSlug );
|
|
130
|
+
|
|
131
|
+
const postIsHierarchical =
|
|
132
|
+
postTypeInfo?.hierarchical && postTypeInfo.viewable;
|
|
133
|
+
|
|
134
|
+
const parentPost = pageId
|
|
135
|
+
? getEntityRecord< BasePost >(
|
|
136
|
+
'postType',
|
|
137
|
+
postTypeSlug,
|
|
138
|
+
pageId
|
|
139
|
+
)
|
|
140
|
+
: null;
|
|
141
|
+
|
|
142
|
+
const query = {
|
|
143
|
+
per_page: 100,
|
|
144
|
+
exclude: postId,
|
|
145
|
+
parent_exclude: postId,
|
|
146
|
+
orderby: 'menu_order',
|
|
147
|
+
order: 'asc',
|
|
148
|
+
_fields: 'id,title,parent',
|
|
149
|
+
...( fieldValue !== null && {
|
|
150
|
+
search: fieldValue,
|
|
151
|
+
} ),
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
isHierarchical: postIsHierarchical,
|
|
156
|
+
parentPostTitle: parentPost
|
|
157
|
+
? getTitleWithFallbackName( parentPost )
|
|
158
|
+
: '',
|
|
159
|
+
pageItems: postIsHierarchical
|
|
160
|
+
? getEntityRecords< BasePost >(
|
|
161
|
+
'postType',
|
|
162
|
+
postTypeSlug,
|
|
163
|
+
query
|
|
164
|
+
)
|
|
165
|
+
: null,
|
|
166
|
+
};
|
|
167
|
+
},
|
|
168
|
+
[ fieldValue, pageId, postId, postTypeSlug ]
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* This logic has been copied from https://github.com/WordPress/gutenberg/blob/0249771b519d5646171fb9fae422006c8ab773f2/packages/editor/src/components/page-attributes/parent.js#L106.
|
|
173
|
+
*/
|
|
174
|
+
const parentOptions = useMemo( () => {
|
|
175
|
+
const getOptionsFromTree = (
|
|
176
|
+
tree: Array< Tree >,
|
|
177
|
+
level = 0
|
|
178
|
+
): Array< {
|
|
179
|
+
value: number;
|
|
180
|
+
label: string;
|
|
181
|
+
rawName: string;
|
|
182
|
+
} > => {
|
|
183
|
+
const mappedNodes = tree.map( ( treeNode ) => [
|
|
184
|
+
{
|
|
185
|
+
value: treeNode.id,
|
|
186
|
+
label:
|
|
187
|
+
'— '.repeat( level ) + decodeEntities( treeNode.name ),
|
|
188
|
+
rawName: treeNode.name,
|
|
189
|
+
},
|
|
190
|
+
...getOptionsFromTree( treeNode.children || [], level + 1 ),
|
|
191
|
+
] );
|
|
192
|
+
|
|
193
|
+
const sortedNodes = mappedNodes.sort( ( [ a ], [ b ] ) => {
|
|
194
|
+
const priorityA = getItemPriority(
|
|
195
|
+
a.rawName,
|
|
196
|
+
fieldValue ?? ''
|
|
197
|
+
);
|
|
198
|
+
const priorityB = getItemPriority(
|
|
199
|
+
b.rawName,
|
|
200
|
+
fieldValue ?? ''
|
|
201
|
+
);
|
|
202
|
+
return priorityA >= priorityB ? 1 : -1;
|
|
203
|
+
} );
|
|
204
|
+
|
|
205
|
+
return sortedNodes.flat();
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
if ( ! pageItems ) {
|
|
209
|
+
return [];
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
let tree = pageItems.map( ( item ) => ( {
|
|
213
|
+
id: item.id as number,
|
|
214
|
+
parent: item.parent ?? null,
|
|
215
|
+
name: getTitleWithFallbackName( item ),
|
|
216
|
+
} ) );
|
|
217
|
+
|
|
218
|
+
// Only build a hierarchical tree when not searching.
|
|
219
|
+
if ( ! fieldValue ) {
|
|
220
|
+
tree = buildTermsTree( tree );
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const opts = getOptionsFromTree( tree );
|
|
224
|
+
|
|
225
|
+
// Ensure the current parent is in the options list.
|
|
226
|
+
const optsHasParent = opts.find( ( item ) => item.value === pageId );
|
|
227
|
+
if ( pageId && parentPostTitle && ! optsHasParent ) {
|
|
228
|
+
opts.unshift( {
|
|
229
|
+
value: pageId,
|
|
230
|
+
label: parentPostTitle,
|
|
231
|
+
rawName: '',
|
|
232
|
+
} );
|
|
233
|
+
}
|
|
234
|
+
return opts.map( ( option ) => ( {
|
|
235
|
+
...option,
|
|
236
|
+
value: option.value.toString(),
|
|
237
|
+
} ) );
|
|
238
|
+
}, [ pageItems, fieldValue, parentPostTitle, pageId ] );
|
|
239
|
+
|
|
240
|
+
if ( ! isHierarchical ) {
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Handle user input.
|
|
246
|
+
*
|
|
247
|
+
* @param {string} inputValue The current value of the input field.
|
|
248
|
+
*/
|
|
249
|
+
const handleKeydown = ( inputValue: string ) => {
|
|
250
|
+
setFieldValue( inputValue );
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Handle author selection.
|
|
255
|
+
*
|
|
256
|
+
* @param {Object} selectedPostId The selected Author.
|
|
257
|
+
*/
|
|
258
|
+
const handleChange = ( selectedPostId: string | null | undefined ) => {
|
|
259
|
+
if ( selectedPostId ) {
|
|
260
|
+
return onChangeControl( parseInt( selectedPostId, 10 ) ?? 0 );
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
onChangeControl( 0 );
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
return (
|
|
267
|
+
<ComboboxControl
|
|
268
|
+
__nextHasNoMarginBottom
|
|
269
|
+
__next40pxDefaultSize
|
|
270
|
+
label={ __( 'Parent' ) }
|
|
271
|
+
help={ __( 'Choose a parent page.' ) }
|
|
272
|
+
value={ pageId?.toString() }
|
|
273
|
+
options={ parentOptions }
|
|
274
|
+
onFilterValueChange={ debounce(
|
|
275
|
+
( value: unknown ) => handleKeydown( value as string ),
|
|
276
|
+
300
|
|
277
|
+
) }
|
|
278
|
+
onChange={ handleChange }
|
|
279
|
+
hideLabelFromVision
|
|
280
|
+
/>
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
export const ParentEdit = ( {
|
|
285
|
+
data,
|
|
286
|
+
field,
|
|
287
|
+
onChange,
|
|
288
|
+
}: DataFormControlProps< BasePost > ) => {
|
|
289
|
+
const { id } = field;
|
|
290
|
+
|
|
291
|
+
const homeUrl = useSelect( ( select ) => {
|
|
292
|
+
// @ts-expect-error getEntityRecord is not typed with unstableBase as argument.
|
|
293
|
+
return select( coreStore ).getEntityRecord< {
|
|
294
|
+
home: string;
|
|
295
|
+
} >( 'root', '__unstableBase' )?.home as string;
|
|
296
|
+
}, [] );
|
|
297
|
+
|
|
298
|
+
const onChangeControl = useCallback(
|
|
299
|
+
( newValue?: number ) =>
|
|
300
|
+
onChange( {
|
|
301
|
+
[ id ]: newValue,
|
|
302
|
+
} ),
|
|
303
|
+
[ id, onChange ]
|
|
304
|
+
);
|
|
305
|
+
|
|
306
|
+
return (
|
|
307
|
+
<fieldset className="fields-controls__parent">
|
|
308
|
+
<div>
|
|
309
|
+
{ createInterpolateElement(
|
|
310
|
+
sprintf(
|
|
311
|
+
/* translators: %1$s The home URL of the WordPress installation without the scheme. */
|
|
312
|
+
__(
|
|
313
|
+
'Child pages inherit characteristics from their parent, such as URL structure. For instance, if "Pricing" is a child of "Services", its URL would be %1$s<wbr />/services<wbr />/pricing.'
|
|
314
|
+
),
|
|
315
|
+
filterURLForDisplay( homeUrl ).replace(
|
|
316
|
+
/([/.])/g,
|
|
317
|
+
'<wbr />$1'
|
|
318
|
+
)
|
|
319
|
+
),
|
|
320
|
+
{
|
|
321
|
+
wbr: <wbr />,
|
|
322
|
+
}
|
|
323
|
+
) }
|
|
324
|
+
<p>
|
|
325
|
+
{ createInterpolateElement(
|
|
326
|
+
__(
|
|
327
|
+
'They also show up as sub-items in the default navigation menu. <a>Learn more.</a>'
|
|
328
|
+
),
|
|
329
|
+
{
|
|
330
|
+
a: (
|
|
331
|
+
<ExternalLink
|
|
332
|
+
href={ __(
|
|
333
|
+
'https://wordpress.org/documentation/article/page-post-settings-sidebar/#page-attributes'
|
|
334
|
+
) }
|
|
335
|
+
children={ undefined }
|
|
336
|
+
/>
|
|
337
|
+
),
|
|
338
|
+
}
|
|
339
|
+
) }
|
|
340
|
+
</p>
|
|
341
|
+
<PageAttributesParent
|
|
342
|
+
data={ data }
|
|
343
|
+
onChangeControl={ onChangeControl }
|
|
344
|
+
/>
|
|
345
|
+
</div>
|
|
346
|
+
</fieldset>
|
|
347
|
+
);
|
|
348
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WordPress dependencies
|
|
3
|
+
*/
|
|
4
|
+
import { useSelect } from '@wordpress/data';
|
|
5
|
+
import { store as coreStore } from '@wordpress/core-data';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Internal dependencies
|
|
9
|
+
*/
|
|
10
|
+
import type { BasePost } from '../../types';
|
|
11
|
+
import type { DataViewRenderFieldProps } from '@wordpress/dataviews';
|
|
12
|
+
import { getTitleWithFallbackName } from './utils';
|
|
13
|
+
import { __ } from '@wordpress/i18n';
|
|
14
|
+
|
|
15
|
+
export const ParentView = ( {
|
|
16
|
+
item,
|
|
17
|
+
}: DataViewRenderFieldProps< BasePost > ) => {
|
|
18
|
+
const parent = useSelect(
|
|
19
|
+
( select ) => {
|
|
20
|
+
const { getEntityRecord } = select( coreStore );
|
|
21
|
+
return item?.parent
|
|
22
|
+
? getEntityRecord( 'postType', item.type, item.parent )
|
|
23
|
+
: null;
|
|
24
|
+
},
|
|
25
|
+
[ item.parent, item.type ]
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
if ( parent ) {
|
|
29
|
+
return <>{ getTitleWithFallbackName( parent ) }</>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return <>{ __( 'None' ) }</>;
|
|
33
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WordPress dependencies
|
|
3
|
+
*/
|
|
4
|
+
import { decodeEntities } from '@wordpress/html-entities';
|
|
5
|
+
import { __ } from '@wordpress/i18n';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Internal dependencies
|
|
9
|
+
*/
|
|
10
|
+
import type { BasePost } from '../../types';
|
|
11
|
+
|
|
12
|
+
export function getTitleWithFallbackName( post: BasePost ) {
|
|
13
|
+
return typeof post.title === 'object' &&
|
|
14
|
+
'rendered' in post.title &&
|
|
15
|
+
post.title.rendered
|
|
16
|
+
? decodeEntities( post.title.rendered )
|
|
17
|
+
: `#${ post?.id } (${ __( 'no title' ) })`;
|
|
18
|
+
}
|