@wordpress/fields 0.2.0 → 0.4.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.
Files changed (202) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/README.md +28 -0
  3. package/build/actions/delete-post.js +4 -4
  4. package/build/actions/delete-post.js.map +1 -1
  5. package/build/actions/duplicate-post.js +2 -2
  6. package/build/actions/duplicate-post.js.map +1 -1
  7. package/build/actions/utils.js +2 -2
  8. package/build/actions/utils.js.map +1 -1
  9. package/build/actions/view-post-revisions.js +1 -1
  10. package/build/actions/view-post-revisions.js.map +1 -1
  11. package/build/fields/comment-status/index.js +40 -0
  12. package/build/fields/comment-status/index.js.map +1 -0
  13. package/build/fields/date/date-view.js +69 -0
  14. package/build/fields/date/date-view.js.map +1 -0
  15. package/build/fields/date/index.js +29 -0
  16. package/build/fields/date/index.js.map +1 -0
  17. package/build/fields/featured-image/featured-image-edit.js +113 -0
  18. package/build/fields/featured-image/featured-image-edit.js.map +1 -0
  19. package/build/fields/featured-image/featured-image-view.js +41 -0
  20. package/build/fields/featured-image/featured-image-view.js.map +1 -0
  21. package/build/fields/featured-image/index.js +27 -0
  22. package/build/fields/featured-image/index.js.map +1 -0
  23. package/build/fields/index.js +49 -0
  24. package/build/fields/index.js.map +1 -1
  25. package/build/fields/order/index.js +1 -1
  26. package/build/fields/order/index.js.map +1 -1
  27. package/build/fields/parent/index.js +31 -0
  28. package/build/fields/parent/index.js.map +1 -0
  29. package/build/fields/parent/parent-edit.js +243 -0
  30. package/build/fields/parent/parent-edit.js.map +1 -0
  31. package/build/fields/parent/parent-view.js +39 -0
  32. package/build/fields/parent/parent-view.js.map +1 -0
  33. package/build/fields/parent/utils.js +20 -0
  34. package/build/fields/parent/utils.js.map +1 -0
  35. package/build/fields/password/edit.js +65 -0
  36. package/build/fields/password/edit.js.map +1 -0
  37. package/build/fields/password/index.js +30 -0
  38. package/build/fields/password/index.js.map +1 -0
  39. package/build/fields/slug/index.js +27 -0
  40. package/build/fields/slug/index.js.map +1 -0
  41. package/build/fields/slug/slug-edit.js +129 -0
  42. package/build/fields/slug/slug-edit.js.map +1 -0
  43. package/build/fields/slug/slug-view.js +31 -0
  44. package/build/fields/slug/slug-view.js.map +1 -0
  45. package/build/fields/slug/utils.js +21 -0
  46. package/build/fields/slug/utils.js.map +1 -0
  47. package/build/fields/status/index.js +37 -0
  48. package/build/fields/status/index.js.map +1 -0
  49. package/build/fields/status/status-elements.js +46 -0
  50. package/build/fields/status/status-elements.js.map +1 -0
  51. package/build/fields/status/status-view.js +41 -0
  52. package/build/fields/status/status-view.js.map +1 -0
  53. package/build/fields/title/index.js +5 -1
  54. package/build/fields/title/index.js.map +1 -1
  55. package/build/fields/title/title-view.js +61 -0
  56. package/build/fields/title/title-view.js.map +1 -0
  57. package/build/mutation/index.js +2 -2
  58. package/build/mutation/index.js.map +1 -1
  59. package/build/types.js.map +1 -1
  60. package/build-module/actions/delete-post.js +5 -5
  61. package/build-module/actions/delete-post.js.map +1 -1
  62. package/build-module/actions/duplicate-post.js +2 -2
  63. package/build-module/actions/duplicate-post.js.map +1 -1
  64. package/build-module/actions/utils.js +2 -2
  65. package/build-module/actions/utils.js.map +1 -1
  66. package/build-module/actions/view-post-revisions.js +1 -1
  67. package/build-module/actions/view-post-revisions.js.map +1 -1
  68. package/build-module/fields/comment-status/index.js +35 -0
  69. package/build-module/fields/comment-status/index.js.map +1 -0
  70. package/build-module/fields/date/date-view.js +62 -0
  71. package/build-module/fields/date/date-view.js.map +1 -0
  72. package/build-module/fields/date/index.js +23 -0
  73. package/build-module/fields/date/index.js.map +1 -0
  74. package/build-module/fields/featured-image/featured-image-edit.js +104 -0
  75. package/build-module/fields/featured-image/featured-image-edit.js.map +1 -0
  76. package/build-module/fields/featured-image/featured-image-view.js +33 -0
  77. package/build-module/fields/featured-image/featured-image-view.js.map +1 -0
  78. package/build-module/fields/featured-image/index.js +22 -0
  79. package/build-module/fields/featured-image/index.js.map +1 -0
  80. package/build-module/fields/index.js +7 -0
  81. package/build-module/fields/index.js.map +1 -1
  82. package/build-module/fields/order/index.js +2 -1
  83. package/build-module/fields/order/index.js.map +1 -1
  84. package/build-module/fields/parent/index.js +26 -0
  85. package/build-module/fields/parent/index.js.map +1 -0
  86. package/build-module/fields/parent/parent-edit.js +232 -0
  87. package/build-module/fields/parent/parent-edit.js.map +1 -0
  88. package/build-module/fields/parent/parent-view.js +32 -0
  89. package/build-module/fields/parent/parent-view.js.map +1 -0
  90. package/build-module/fields/parent/utils.js +14 -0
  91. package/build-module/fields/parent/utils.js.map +1 -0
  92. package/build-module/fields/password/edit.js +58 -0
  93. package/build-module/fields/password/edit.js.map +1 -0
  94. package/build-module/fields/password/index.js +23 -0
  95. package/build-module/fields/password/index.js.map +1 -0
  96. package/build-module/fields/slug/index.js +21 -0
  97. package/build-module/fields/slug/index.js.map +1 -0
  98. package/build-module/fields/slug/slug-edit.js +123 -0
  99. package/build-module/fields/slug/slug-edit.js.map +1 -0
  100. package/build-module/fields/slug/slug-view.js +25 -0
  101. package/build-module/fields/slug/slug-view.js.map +1 -0
  102. package/build-module/fields/slug/utils.js +13 -0
  103. package/build-module/fields/slug/utils.js.map +1 -0
  104. package/build-module/fields/status/index.js +31 -0
  105. package/build-module/fields/status/index.js.map +1 -0
  106. package/build-module/fields/status/status-elements.js +40 -0
  107. package/build-module/fields/status/status-elements.js.map +1 -0
  108. package/build-module/fields/status/status-view.js +34 -0
  109. package/build-module/fields/status/status-view.js.map +1 -0
  110. package/build-module/fields/title/index.js +4 -1
  111. package/build-module/fields/title/index.js.map +1 -1
  112. package/build-module/fields/title/title-view.js +55 -0
  113. package/build-module/fields/title/title-view.js.map +1 -0
  114. package/build-module/mutation/index.js +2 -2
  115. package/build-module/mutation/index.js.map +1 -1
  116. package/build-module/types.js.map +1 -1
  117. package/build-style/style-rtl.css +213 -0
  118. package/build-style/style.css +213 -0
  119. package/build-types/actions/delete-post.d.ts.map +1 -1
  120. package/build-types/fields/comment-status/index.d.ts +14 -0
  121. package/build-types/fields/comment-status/index.d.ts.map +1 -0
  122. package/build-types/fields/date/date-view.d.ts +9 -0
  123. package/build-types/fields/date/date-view.d.ts.map +1 -0
  124. package/build-types/fields/date/index.d.ts +14 -0
  125. package/build-types/fields/date/index.d.ts.map +1 -0
  126. package/build-types/fields/featured-image/featured-image-edit.d.ts +7 -0
  127. package/build-types/fields/featured-image/featured-image-edit.d.ts.map +1 -0
  128. package/build-types/fields/featured-image/featured-image-view.d.ts +7 -0
  129. package/build-types/fields/featured-image/featured-image-view.d.ts.map +1 -0
  130. package/build-types/fields/featured-image/index.d.ts +11 -0
  131. package/build-types/fields/featured-image/index.d.ts.map +1 -0
  132. package/build-types/fields/index.d.ts +7 -0
  133. package/build-types/fields/index.d.ts.map +1 -1
  134. package/build-types/fields/order/index.d.ts.map +1 -1
  135. package/build-types/fields/parent/index.d.ts +14 -0
  136. package/build-types/fields/parent/index.d.ts.map +1 -0
  137. package/build-types/fields/parent/parent-edit.d.ts +12 -0
  138. package/build-types/fields/parent/parent-edit.d.ts.map +1 -0
  139. package/build-types/fields/parent/parent-view.d.ts +7 -0
  140. package/build-types/fields/parent/parent-view.d.ts.map +1 -0
  141. package/build-types/fields/parent/utils.d.ts +6 -0
  142. package/build-types/fields/parent/utils.d.ts.map +1 -0
  143. package/build-types/fields/password/edit.d.ts +8 -0
  144. package/build-types/fields/password/edit.d.ts.map +1 -0
  145. package/build-types/fields/password/index.d.ts +14 -0
  146. package/build-types/fields/password/index.d.ts.map +1 -0
  147. package/build-types/fields/slug/index.d.ts +11 -0
  148. package/build-types/fields/slug/index.d.ts.map +1 -0
  149. package/build-types/fields/slug/slug-edit.d.ts +8 -0
  150. package/build-types/fields/slug/slug-edit.d.ts.map +1 -0
  151. package/build-types/fields/slug/slug-view.d.ts +9 -0
  152. package/build-types/fields/slug/slug-view.d.ts.map +1 -0
  153. package/build-types/fields/slug/utils.d.ts +6 -0
  154. package/build-types/fields/slug/utils.d.ts.map +1 -0
  155. package/build-types/fields/status/index.d.ts +14 -0
  156. package/build-types/fields/status/index.d.ts.map +1 -0
  157. package/build-types/fields/status/status-elements.d.ts +13 -0
  158. package/build-types/fields/status/status-elements.d.ts.map +1 -0
  159. package/build-types/fields/status/status-view.d.ts +9 -0
  160. package/build-types/fields/status/status-view.d.ts.map +1 -0
  161. package/build-types/fields/title/index.d.ts.map +1 -1
  162. package/build-types/fields/title/title-view.d.ts +9 -0
  163. package/build-types/fields/title/title-view.d.ts.map +1 -0
  164. package/build-types/lock-unlock.d.ts +1 -1
  165. package/build-types/lock-unlock.d.ts.map +1 -1
  166. package/build-types/types.d.ts +5 -1
  167. package/build-types/types.d.ts.map +1 -1
  168. package/package.json +27 -22
  169. package/src/actions/delete-post.tsx +8 -5
  170. package/src/actions/duplicate-post.tsx +2 -2
  171. package/src/actions/utils.ts +2 -2
  172. package/src/actions/view-post-revisions.tsx +1 -1
  173. package/src/fields/comment-status/index.tsx +40 -0
  174. package/src/fields/date/date-view.tsx +92 -0
  175. package/src/fields/date/index.tsx +23 -0
  176. package/src/fields/featured-image/featured-image-edit.tsx +123 -0
  177. package/src/fields/featured-image/featured-image-view.tsx +38 -0
  178. package/src/fields/featured-image/index.ts +23 -0
  179. package/src/fields/featured-image/style.scss +95 -0
  180. package/src/fields/index.ts +7 -0
  181. package/src/fields/order/index.ts +2 -1
  182. package/src/fields/parent/index.ts +26 -0
  183. package/src/fields/parent/parent-edit.tsx +348 -0
  184. package/src/fields/parent/parent-view.tsx +33 -0
  185. package/src/fields/parent/utils.ts +18 -0
  186. package/src/fields/password/edit.tsx +68 -0
  187. package/src/fields/password/index.tsx +24 -0
  188. package/src/fields/slug/index.ts +22 -0
  189. package/src/fields/slug/slug-edit.tsx +153 -0
  190. package/src/fields/slug/slug-view.tsx +27 -0
  191. package/src/fields/slug/style.scss +22 -0
  192. package/src/fields/slug/utils.ts +15 -0
  193. package/src/fields/status/index.tsx +32 -0
  194. package/src/fields/status/status-elements.tsx +50 -0
  195. package/src/fields/status/status-view.tsx +28 -0
  196. package/src/fields/title/index.ts +3 -0
  197. package/src/fields/title/title-view.tsx +62 -0
  198. package/src/mutation/index.ts +3 -3
  199. package/src/style.scss +2 -0
  200. package/src/types.ts +5 -1
  201. package/tsconfig.json +10 -7
  202. package/tsconfig.tsbuildinfo +1 -1
@@ -0,0 +1,348 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import removeAccents from 'remove-accents';
5
+
6
+ /**
7
+ * WordPress dependencies
8
+ */
9
+ import { ComboboxControl, ExternalLink } from '@wordpress/components';
10
+ import { useSelect } from '@wordpress/data';
11
+ import {
12
+ createInterpolateElement,
13
+ useCallback,
14
+ useMemo,
15
+ useState,
16
+ } from '@wordpress/element';
17
+ // @ts-ignore
18
+ import { store as coreStore } from '@wordpress/core-data';
19
+ import type { DataFormControlProps } from '@wordpress/dataviews';
20
+ import { debounce } from '@wordpress/compose';
21
+ import { decodeEntities } from '@wordpress/html-entities';
22
+ import { __, sprintf } from '@wordpress/i18n';
23
+ import { filterURLForDisplay } from '@wordpress/url';
24
+
25
+ /**
26
+ * Internal dependencies
27
+ */
28
+ import type { BasePost } from '../../types';
29
+ import { getTitleWithFallbackName } from './utils';
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
+ import type { DataViewRenderFieldProps } from '@wordpress/dataviews';
7
+ import { __ } from '@wordpress/i18n';
8
+
9
+ /**
10
+ * Internal dependencies
11
+ */
12
+ import type { BasePost } from '../../types';
13
+ import { getTitleWithFallbackName } from './utils';
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
+ }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import {
5
+ CheckboxControl,
6
+ __experimentalVStack as VStack,
7
+ TextControl,
8
+ } from '@wordpress/components';
9
+ import type { DataFormControlProps } from '@wordpress/dataviews';
10
+ import { useState } from '@wordpress/element';
11
+ import { __ } from '@wordpress/i18n';
12
+
13
+ /**
14
+ * Internal dependencies
15
+ */
16
+ import type { BasePost } from '../../types';
17
+
18
+ function PasswordEdit( {
19
+ data,
20
+ onChange,
21
+ field,
22
+ }: DataFormControlProps< BasePost > ) {
23
+ const [ showPassword, setShowPassword ] = useState(
24
+ !! field.getValue( { item: data } )
25
+ );
26
+
27
+ const handleTogglePassword = ( value: boolean ) => {
28
+ setShowPassword( value );
29
+ if ( ! value ) {
30
+ onChange( { password: '' } );
31
+ }
32
+ };
33
+
34
+ return (
35
+ <VStack
36
+ as="fieldset"
37
+ spacing={ 4 }
38
+ className="fields-controls__password"
39
+ >
40
+ <CheckboxControl
41
+ __nextHasNoMarginBottom
42
+ label={ __( 'Password protected' ) }
43
+ help={ __( 'Only visible to those who know the password' ) }
44
+ checked={ showPassword }
45
+ onChange={ handleTogglePassword }
46
+ />
47
+ { showPassword && (
48
+ <div className="fields-controls__password-input">
49
+ <TextControl
50
+ label={ __( 'Password' ) }
51
+ onChange={ ( value ) =>
52
+ onChange( {
53
+ password: value,
54
+ } )
55
+ }
56
+ value={ field.getValue( { item: data } ) || '' }
57
+ placeholder={ __( 'Use a secure password' ) }
58
+ type="text"
59
+ __next40pxDefaultSize
60
+ __nextHasNoMarginBottom
61
+ maxLength={ 255 }
62
+ />
63
+ </div>
64
+ ) }
65
+ </VStack>
66
+ );
67
+ }
68
+ export default PasswordEdit;
@@ -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 PasswordEdit from './edit';
11
+
12
+ const passwordField: Field< BasePost > = {
13
+ id: 'password',
14
+ type: 'text',
15
+ Edit: PasswordEdit,
16
+ enableSorting: false,
17
+ enableHiding: false,
18
+ isVisible: ( item ) => item.status !== 'private',
19
+ };
20
+
21
+ /**
22
+ * This field is used to display the post password.
23
+ */
24
+ export default passwordField;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import type { Field } from '@wordpress/dataviews';
5
+ import { __ } from '@wordpress/i18n';
6
+
7
+ /**
8
+ * Internal dependencies
9
+ */
10
+ import type { BasePost } from '../../types';
11
+ import SlugEdit from './slug-edit';
12
+ import SlugView from './slug-view';
13
+
14
+ const slugField: Field< BasePost > = {
15
+ id: 'slug',
16
+ type: 'text',
17
+ label: __( 'Slug' ),
18
+ Edit: SlugEdit,
19
+ render: SlugView,
20
+ };
21
+
22
+ export default slugField;
@@ -0,0 +1,153 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import {
5
+ Button,
6
+ ExternalLink,
7
+ __experimentalInputControl as InputControl,
8
+ __experimentalInputControlPrefixWrapper as InputControlPrefixWrapper,
9
+ __experimentalVStack as VStack,
10
+ } from '@wordpress/components';
11
+ import { copySmall } from '@wordpress/icons';
12
+ import { useCopyToClipboard, useInstanceId } from '@wordpress/compose';
13
+ import { useDispatch } from '@wordpress/data';
14
+ import { useCallback, useEffect, useRef } from '@wordpress/element';
15
+ import { store as noticesStore } from '@wordpress/notices';
16
+ import { safeDecodeURIComponent } from '@wordpress/url';
17
+ import type { DataFormControlProps } from '@wordpress/dataviews';
18
+ import { __ } from '@wordpress/i18n';
19
+
20
+ /**
21
+ * Internal dependencies
22
+ */
23
+ import type { BasePost } from '../../types';
24
+ import { getSlug } from './utils';
25
+
26
+ const SlugEdit = ( {
27
+ field,
28
+ onChange,
29
+ data,
30
+ }: DataFormControlProps< BasePost > ) => {
31
+ const { id } = field;
32
+
33
+ const slug = field.getValue( { item: data } ) || getSlug( data );
34
+ const permalinkTemplate = data.permalink_template || '';
35
+ const PERMALINK_POSTNAME_REGEX = /%(?:postname|pagename)%/;
36
+ const [ prefix, suffix ] = permalinkTemplate.split(
37
+ PERMALINK_POSTNAME_REGEX
38
+ );
39
+ const permalinkPrefix = prefix;
40
+ const permalinkSuffix = suffix;
41
+ const isEditable = PERMALINK_POSTNAME_REGEX.test( permalinkTemplate );
42
+ const originalSlugRef = useRef( slug );
43
+ const slugToDisplay = slug || originalSlugRef.current;
44
+ const permalink = isEditable
45
+ ? `${ permalinkPrefix }${ slugToDisplay }${ permalinkSuffix }`
46
+ : safeDecodeURIComponent( data.link || '' );
47
+
48
+ useEffect( () => {
49
+ if ( slug && originalSlugRef.current === undefined ) {
50
+ originalSlugRef.current = slug;
51
+ }
52
+ }, [ slug ] );
53
+
54
+ const onChangeControl = useCallback(
55
+ ( newValue?: string ) =>
56
+ onChange( {
57
+ [ id ]: newValue,
58
+ } ),
59
+ [ id, onChange ]
60
+ );
61
+
62
+ const { createNotice } = useDispatch( noticesStore );
63
+
64
+ const copyButtonRef = useCopyToClipboard( permalink, () => {
65
+ createNotice( 'info', __( 'Copied Permalink to clipboard.' ), {
66
+ isDismissible: true,
67
+ type: 'snackbar',
68
+ } );
69
+ } );
70
+
71
+ const postUrlSlugDescriptionId =
72
+ 'editor-post-url__slug-description-' + useInstanceId( SlugEdit );
73
+
74
+ return (
75
+ <fieldset className="fields-controls__slug">
76
+ { isEditable && (
77
+ <VStack>
78
+ <VStack spacing="0px">
79
+ <span>
80
+ { __(
81
+ 'Customize the last part of the Permalink.'
82
+ ) }
83
+ </span>
84
+ <ExternalLink href="https://wordpress.org/documentation/article/page-post-settings-sidebar/#permalink">
85
+ { __( 'Learn more' ) }
86
+ </ExternalLink>
87
+ </VStack>
88
+ <InputControl
89
+ __next40pxDefaultSize
90
+ prefix={
91
+ <InputControlPrefixWrapper>
92
+ /
93
+ </InputControlPrefixWrapper>
94
+ }
95
+ suffix={
96
+ <Button
97
+ __next40pxDefaultSize
98
+ icon={ copySmall }
99
+ ref={ copyButtonRef }
100
+ label={ __( 'Copy' ) }
101
+ />
102
+ }
103
+ label={ __( 'Link' ) }
104
+ hideLabelFromVision
105
+ value={ slug }
106
+ autoComplete="off"
107
+ spellCheck="false"
108
+ type="text"
109
+ className="fields-controls__slug-input"
110
+ onChange={ ( newValue?: string ) => {
111
+ onChangeControl( newValue );
112
+ } }
113
+ onBlur={ () => {
114
+ if ( slug === '' ) {
115
+ onChangeControl( originalSlugRef.current );
116
+ }
117
+ } }
118
+ aria-describedby={ postUrlSlugDescriptionId }
119
+ />
120
+ <div className="fields-controls__slug-help">
121
+ <span className="fields-controls__slug-help-visual-label">
122
+ { __( 'Permalink:' ) }
123
+ </span>
124
+ <ExternalLink
125
+ className="fields-controls__slug-help-link"
126
+ href={ permalink }
127
+ >
128
+ <span className="fields-controls__slug-help-prefix">
129
+ { permalinkPrefix }
130
+ </span>
131
+ <span className="fields-controls__slug-help-slug">
132
+ { slugToDisplay }
133
+ </span>
134
+ <span className="fields-controls__slug-help-suffix">
135
+ { permalinkSuffix }
136
+ </span>
137
+ </ExternalLink>
138
+ </div>
139
+ </VStack>
140
+ ) }
141
+ { ! isEditable && (
142
+ <ExternalLink
143
+ className="fields-controls__slug-help"
144
+ href={ permalink }
145
+ >
146
+ { permalink }
147
+ </ExternalLink>
148
+ ) }
149
+ </fieldset>
150
+ );
151
+ };
152
+
153
+ export default SlugEdit;
@@ -0,0 +1,27 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { useEffect, useRef } from '@wordpress/element';
5
+
6
+ /**
7
+ * Internal dependencies
8
+ */
9
+ import type { BasePost } from '../../types';
10
+ import { getSlug } from './utils';
11
+
12
+ const SlugView = ( { item }: { item: BasePost } ) => {
13
+ const slug = typeof item === 'object' ? getSlug( item ) : '';
14
+ const originalSlugRef = useRef( slug );
15
+
16
+ useEffect( () => {
17
+ if ( slug && originalSlugRef.current === undefined ) {
18
+ originalSlugRef.current = slug;
19
+ }
20
+ }, [ slug ] );
21
+
22
+ const slugToDisplay = slug || originalSlugRef.current;
23
+
24
+ return `${ slugToDisplay }`;
25
+ };
26
+
27
+ export default SlugView;