@wordpress/editor 14.33.3 → 14.33.5

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 (107) hide show
  1. package/build/bindings/post-data.js +47 -63
  2. package/build/bindings/post-data.js.map +3 -3
  3. package/build/bindings/post-meta.js +45 -39
  4. package/build/bindings/post-meta.js.map +2 -2
  5. package/build/components/collab-sidebar/add-comment.js +9 -5
  6. package/build/components/collab-sidebar/add-comment.js.map +2 -2
  7. package/build/components/collab-sidebar/comment-author-info.js +27 -15
  8. package/build/components/collab-sidebar/comment-author-info.js.map +2 -2
  9. package/build/components/collab-sidebar/comment-indicator-toolbar.js +9 -23
  10. package/build/components/collab-sidebar/comment-indicator-toolbar.js.map +3 -3
  11. package/build/components/collab-sidebar/comment-menu-item.js +36 -6
  12. package/build/components/collab-sidebar/comment-menu-item.js.map +3 -3
  13. package/build/components/collab-sidebar/comments.js +317 -301
  14. package/build/components/collab-sidebar/comments.js.map +3 -3
  15. package/build/components/collab-sidebar/hooks.js +5 -3
  16. package/build/components/collab-sidebar/hooks.js.map +2 -2
  17. package/build/components/collab-sidebar/index.js +35 -11
  18. package/build/components/collab-sidebar/index.js.map +3 -3
  19. package/build/components/collab-sidebar/utils.js +6 -3
  20. package/build/components/collab-sidebar/utils.js.map +2 -2
  21. package/build/components/editor/index.js +2 -2
  22. package/build/components/editor/index.js.map +3 -3
  23. package/build/components/more-menu/index.js +1 -1
  24. package/build/components/more-menu/index.js.map +2 -2
  25. package/build/components/visual-editor/index.js +20 -9
  26. package/build/components/visual-editor/index.js.map +2 -2
  27. package/build/store/private-actions.js +8 -0
  28. package/build/store/private-actions.js.map +2 -2
  29. package/build/store/private-selectors.js +5 -0
  30. package/build/store/private-selectors.js.map +2 -2
  31. package/build/store/reducer.js +10 -0
  32. package/build/store/reducer.js.map +2 -2
  33. package/build-module/bindings/post-data.js +47 -63
  34. package/build-module/bindings/post-data.js.map +2 -2
  35. package/build-module/bindings/post-meta.js +45 -39
  36. package/build-module/bindings/post-meta.js.map +2 -2
  37. package/build-module/components/collab-sidebar/add-comment.js +10 -6
  38. package/build-module/components/collab-sidebar/add-comment.js.map +2 -2
  39. package/build-module/components/collab-sidebar/comment-author-info.js +27 -15
  40. package/build-module/components/collab-sidebar/comment-author-info.js.map +2 -2
  41. package/build-module/components/collab-sidebar/comment-indicator-toolbar.js +15 -25
  42. package/build-module/components/collab-sidebar/comment-indicator-toolbar.js.map +2 -2
  43. package/build-module/components/collab-sidebar/comment-menu-item.js +40 -7
  44. package/build-module/components/collab-sidebar/comment-menu-item.js.map +2 -2
  45. package/build-module/components/collab-sidebar/comments.js +319 -302
  46. package/build-module/components/collab-sidebar/comments.js.map +2 -2
  47. package/build-module/components/collab-sidebar/hooks.js +5 -3
  48. package/build-module/components/collab-sidebar/hooks.js.map +2 -2
  49. package/build-module/components/collab-sidebar/index.js +35 -11
  50. package/build-module/components/collab-sidebar/index.js.map +2 -2
  51. package/build-module/components/collab-sidebar/utils.js +6 -3
  52. package/build-module/components/collab-sidebar/utils.js.map +2 -2
  53. package/build-module/components/editor/index.js +2 -2
  54. package/build-module/components/editor/index.js.map +2 -2
  55. package/build-module/components/more-menu/index.js +1 -1
  56. package/build-module/components/more-menu/index.js.map +2 -2
  57. package/build-module/components/visual-editor/index.js +20 -9
  58. package/build-module/components/visual-editor/index.js.map +2 -2
  59. package/build-module/store/private-actions.js +7 -0
  60. package/build-module/store/private-actions.js.map +2 -2
  61. package/build-module/store/private-selectors.js +4 -0
  62. package/build-module/store/private-selectors.js.map +2 -2
  63. package/build-module/store/reducer.js +9 -0
  64. package/build-module/store/reducer.js.map +2 -2
  65. package/build-style/style-rtl.css +6 -43
  66. package/build-style/style.css +6 -43
  67. package/build-types/bindings/post-data.d.ts +18 -8
  68. package/build-types/bindings/post-meta.d.ts +1 -7
  69. package/build-types/components/collab-sidebar/add-comment.d.ts.map +1 -1
  70. package/build-types/components/collab-sidebar/comment-author-info.d.ts +5 -16
  71. package/build-types/components/collab-sidebar/comment-author-info.d.ts.map +1 -1
  72. package/build-types/components/collab-sidebar/comment-indicator-toolbar.d.ts.map +1 -1
  73. package/build-types/components/collab-sidebar/comment-menu-item.d.ts +3 -2
  74. package/build-types/components/collab-sidebar/comment-menu-item.d.ts.map +1 -1
  75. package/build-types/components/collab-sidebar/comments.d.ts +1 -1
  76. package/build-types/components/collab-sidebar/comments.d.ts.map +1 -1
  77. package/build-types/components/collab-sidebar/hooks.d.ts.map +1 -1
  78. package/build-types/components/collab-sidebar/index.d.ts.map +1 -1
  79. package/build-types/components/collab-sidebar/utils.d.ts +2 -2
  80. package/build-types/components/collab-sidebar/utils.d.ts.map +1 -1
  81. package/build-types/components/visual-editor/index.d.ts.map +1 -1
  82. package/build-types/store/private-actions.d.ts +7 -0
  83. package/build-types/store/private-actions.d.ts.map +1 -1
  84. package/build-types/store/private-selectors.d.ts +7 -0
  85. package/build-types/store/private-selectors.d.ts.map +1 -1
  86. package/build-types/store/reducer.d.ts +10 -0
  87. package/build-types/store/reducer.d.ts.map +1 -1
  88. package/package.json +8 -8
  89. package/src/bindings/post-data.js +63 -111
  90. package/src/bindings/post-meta.js +55 -46
  91. package/src/bindings/test/post-meta.js +211 -0
  92. package/src/components/collab-sidebar/add-comment.js +11 -6
  93. package/src/components/collab-sidebar/comment-author-info.js +33 -26
  94. package/src/components/collab-sidebar/comment-indicator-toolbar.js +19 -29
  95. package/src/components/collab-sidebar/comment-menu-item.js +51 -11
  96. package/src/components/collab-sidebar/comments.js +47 -27
  97. package/src/components/collab-sidebar/hooks.js +6 -4
  98. package/src/components/collab-sidebar/index.js +63 -27
  99. package/src/components/collab-sidebar/style.scss +6 -46
  100. package/src/components/collab-sidebar/utils.js +15 -5
  101. package/src/components/editor/index.js +1 -1
  102. package/src/components/more-menu/index.js +1 -1
  103. package/src/components/visual-editor/index.js +27 -6
  104. package/src/store/private-actions.js +13 -0
  105. package/src/store/private-selectors.js +10 -0
  106. package/src/store/reducer.js +16 -0
  107. package/tsconfig.tsbuildinfo +1 -1
@@ -11,90 +11,23 @@ const NAVIGATION_BLOCK_TYPES = [
11
11
  'core/navigation-submenu',
12
12
  ];
13
13
 
14
- /**
15
- * Gets a list of post data fields with their values and labels
16
- * to be consumed in the needed callbacks.
17
- * If the value is not available based on context, like in templates,
18
- * it falls back to the default value, label, or key.
19
- *
20
- * @param {Object} select The select function from the data store.
21
- * @param {Object} context The context provided.
22
- * @param {string} clientId The block client ID used to read attributes.
23
- * @return {Object} List of post data fields with their value and label.
24
- *
25
- * @example
26
- * ```js
27
- * {
28
- * field_1_key: {
29
- * label: 'Field 1 Label',
30
- * value: 'Field 1 Value',
31
- * },
32
- * field_2_key: {
33
- * label: 'Field 2 Label',
34
- * value: 'Field 2 Value',
35
- * },
36
- * ...
37
- * }
38
- * ```
39
- */
40
- function getPostDataFields( select, context, clientId ) {
41
- const { getEditedEntityRecord } = select( coreDataStore );
42
- const { getBlockAttributes, getBlockName } = select( blockEditorStore );
43
-
44
- let entityDataValues, dataFields;
45
-
46
- /*
47
- * BACKWARDS COMPATIBILITY: Hardcoded exception for navigation blocks.
48
- * Required for WordPress 6.9+ navigation blocks. DO NOT REMOVE.
49
- */
50
- const blockName = getBlockName?.( clientId );
51
- const isNavigationBlock = NAVIGATION_BLOCK_TYPES.includes( blockName );
52
-
53
- let postId, postType;
54
-
55
- if ( isNavigationBlock ) {
56
- // Navigation blocks: read from block attributes
57
- const blockAttributes = getBlockAttributes?.( clientId );
58
- postId = blockAttributes?.id;
59
- postType = blockAttributes?.type;
60
- } else {
61
- // All other blocks: use context
62
- postId = context?.postId;
63
- postType = context?.postType;
64
- }
65
-
66
- // Try to get the current entity data values using resolved identifiers.
67
- if ( postType && postId ) {
68
- entityDataValues = getEditedEntityRecord(
69
- 'postType',
70
- postType,
71
- postId
72
- );
73
- dataFields = {
74
- date: {
75
- label: __( 'Post Date' ),
76
- value: entityDataValues?.date,
77
- type: 'string',
78
- },
79
- modified: {
80
- label: __( 'Post Modified Date' ),
81
- value: entityDataValues?.modified,
82
- type: 'string',
83
- },
84
- link: {
85
- label: __( 'Post Link' ),
86
- value: entityDataValues?.link,
87
- type: 'string',
88
- },
89
- };
90
- }
91
-
92
- if ( ! Object.keys( dataFields || {} ).length ) {
93
- return null;
94
- }
95
-
96
- return dataFields;
97
- }
14
+ const postDataFields = [
15
+ {
16
+ label: __( 'Post Date' ),
17
+ args: { field: 'date' },
18
+ type: 'string',
19
+ },
20
+ {
21
+ label: __( 'Post Modified Date' ),
22
+ args: { field: 'modified' },
23
+ type: 'string',
24
+ },
25
+ {
26
+ label: __( 'Post Link' ),
27
+ args: { field: 'link' },
28
+ type: 'string',
29
+ },
30
+ ];
98
31
 
99
32
  /**
100
33
  * @type {WPBlockBindingsSource}
@@ -102,15 +35,50 @@ function getPostDataFields( select, context, clientId ) {
102
35
  export default {
103
36
  name: 'core/post-data',
104
37
  getValues( { select, context, bindings, clientId } ) {
105
- const dataFields = getPostDataFields( select, context, clientId );
38
+ const allowedFields = postDataFields.map(
39
+ ( field ) => field.args.field
40
+ );
41
+
42
+ /*
43
+ * BACKWARDS COMPATIBILITY: Hardcoded exception for navigation blocks.
44
+ * Required for WordPress 6.9+ navigation blocks. DO NOT REMOVE.
45
+ */
46
+ const { getBlockAttributes, getBlockName } = select( blockEditorStore );
47
+ const blockName = getBlockName?.( clientId );
48
+ const isNavigationBlock = NAVIGATION_BLOCK_TYPES.includes( blockName );
49
+
50
+ let postId, postType;
51
+
52
+ if ( isNavigationBlock ) {
53
+ // Navigation blocks: read from block attributes
54
+ const blockAttributes = getBlockAttributes?.( clientId );
55
+ postId = blockAttributes?.id;
56
+ postType = blockAttributes?.type;
57
+ } else {
58
+ // All other blocks: use context
59
+ postId = context?.postId;
60
+ postType = context?.postType;
61
+ }
62
+
63
+ const { getEditedEntityRecord } = select( coreDataStore );
64
+ const entityDataValues = getEditedEntityRecord(
65
+ 'postType',
66
+ postType,
67
+ postId
68
+ );
106
69
 
107
70
  const newValues = {};
108
- for ( const [ attributeName, source ] of Object.entries( bindings ) ) {
109
- // Use the value, the field label, or the field key.
110
- const fieldKey = source.args.field;
111
- const { value: fieldValue, label: fieldLabel } =
112
- dataFields?.[ fieldKey ] || {};
113
- newValues[ attributeName ] = fieldValue ?? fieldLabel ?? fieldKey;
71
+ for ( const [ attributeName, binding ] of Object.entries( bindings ) ) {
72
+ if ( ! allowedFields.includes( binding.args.field ) ) {
73
+ newValues[ attributeName ] = {};
74
+ continue;
75
+ }
76
+
77
+ newValues[ attributeName ] =
78
+ entityDataValues?.[ binding.args.field ] ??
79
+ postDataFields.find(
80
+ ( field ) => field.args.field === binding.args.field
81
+ ).label;
114
82
  }
115
83
  return newValues;
116
84
  },
@@ -136,7 +104,7 @@ export default {
136
104
  newData
137
105
  );
138
106
  },
139
- canUserEditValue( { select, context, args } ) {
107
+ canUserEditValue( { select, context } ) {
140
108
  const { getBlockName, getSelectedBlockClientId } =
141
109
  select( blockEditorStore );
142
110
  const clientId = getSelectedBlockClientId();
@@ -158,14 +126,6 @@ export default {
158
126
  return false;
159
127
  }
160
128
 
161
- const fieldValue = getPostDataFields( select, context, undefined )?.[
162
- args.field
163
- ]?.value;
164
- // Empty string or `false` could be a valid value, so we need to check if the field value is undefined.
165
- if ( fieldValue === undefined ) {
166
- return false;
167
- }
168
-
169
129
  // Check that the user has the capability to edit post data.
170
130
  const canUserEdit = select( coreDataStore ).canUser( 'update', {
171
131
  kind: 'postType',
@@ -178,7 +138,7 @@ export default {
178
138
 
179
139
  return true;
180
140
  },
181
- getFieldsList( { select, context } ) {
141
+ getFieldsList( { select } ) {
182
142
  const selectedBlock = select( blockEditorStore ).getSelectedBlock();
183
143
  if ( selectedBlock?.name !== 'core/post-date' ) {
184
144
  return [];
@@ -187,15 +147,7 @@ export default {
187
147
  if ( NAVIGATION_BLOCK_TYPES.includes( selectedBlock?.name ) ) {
188
148
  return [];
189
149
  }
190
- const clientId = select( blockEditorStore ).getSelectedBlockClientId();
191
- const postDataFields = getPostDataFields( select, context, clientId );
192
- if ( ! postDataFields ) {
193
- return [];
194
- }
195
- return Object.entries( postDataFields ).map( ( [ key, field ] ) => ( {
196
- label: field.label,
197
- type: field.type,
198
- args: { field: key },
199
- } ) );
150
+
151
+ return postDataFields;
200
152
  },
201
153
  };
@@ -35,41 +35,52 @@ import { unlock } from '../lock-unlock';
35
35
  * ```
36
36
  */
37
37
  function getPostMetaFields( select, context ) {
38
- const { getEditedEntityRecord } = select( coreDataStore );
39
38
  const { getRegisteredPostMeta } = unlock( select( coreDataStore ) );
40
39
 
41
- let entityMetaValues;
42
- // Try to get the current entity meta values.
43
- if ( context?.postType && context?.postId ) {
44
- entityMetaValues = getEditedEntityRecord(
45
- 'postType',
46
- context?.postType,
47
- context?.postId
48
- ).meta;
49
- }
50
-
51
40
  const registeredFields = getRegisteredPostMeta( context?.postType );
52
- const metaFields = {};
53
- Object.entries( registeredFields || {} ).forEach( ( [ key, props ] ) => {
41
+ const metaFields = [];
42
+ Object.entries( registeredFields ).forEach( ( [ key, props ] ) => {
54
43
  // Don't include footnotes or private fields.
55
- if ( key !== 'footnotes' && key.charAt( 0 ) !== '_' ) {
56
- metaFields[ key ] = {
57
- label: props.title || key,
58
- value:
59
- // When using the entity value, an empty string IS a valid value.
60
- entityMetaValues?.[ key ] ??
61
- // When using the default, an empty string IS NOT a valid value.
62
- ( props.default || undefined ),
63
- type: props.type,
64
- };
44
+ if ( key === 'footnotes' || key.charAt( 0 ) === '_' ) {
45
+ return;
65
46
  }
47
+
48
+ metaFields.push( {
49
+ label: props.title || key,
50
+ args: { key },
51
+ default: props.default,
52
+ type: props.type,
53
+ } );
66
54
  } );
67
55
 
68
- if ( ! Object.keys( metaFields || {} ).length ) {
69
- return null;
56
+ return metaFields;
57
+ }
58
+
59
+ function getValue( { select, context, args } ) {
60
+ const metaFields = getPostMetaFields( select, context );
61
+ const metaField = metaFields.find(
62
+ ( field ) => field.args.key === args.key
63
+ );
64
+
65
+ // If the meta field was not found, it's either protected, inaccessible, or simply doesn't exist.
66
+ if ( ! metaField ) {
67
+ return args.key;
70
68
  }
71
69
 
72
- return metaFields;
70
+ // Without a postId, we cannot look up a meta value.
71
+ if ( ! context?.postId ) {
72
+ // Return the default value for the meta field if available.
73
+ return metaField.default || metaField.label || args.key;
74
+ }
75
+
76
+ const { getEditedEntityRecord } = select( coreDataStore );
77
+ const entityMetaValues = getEditedEntityRecord(
78
+ 'postType',
79
+ context?.postType,
80
+ context?.postId
81
+ ).meta;
82
+
83
+ return entityMetaValues?.[ args.key ] ?? metaField?.label ?? args.key;
73
84
  }
74
85
 
75
86
  /**
@@ -78,15 +89,13 @@ function getPostMetaFields( select, context ) {
78
89
  export default {
79
90
  name: 'core/post-meta',
80
91
  getValues( { select, context, bindings } ) {
81
- const metaFields = getPostMetaFields( select, context );
82
-
83
92
  const newValues = {};
84
- for ( const [ attributeName, source ] of Object.entries( bindings ) ) {
85
- // Use the value, the field label, or the field key.
86
- const fieldKey = source.args.key;
87
- const { value: fieldValue, label: fieldLabel } =
88
- metaFields?.[ fieldKey ] || {};
89
- newValues[ attributeName ] = fieldValue ?? fieldLabel ?? fieldKey;
93
+ for ( const [ attributeName, binding ] of Object.entries( bindings ) ) {
94
+ newValues[ attributeName ] = getValue( {
95
+ select,
96
+ context,
97
+ args: binding.args,
98
+ } );
90
99
  }
91
100
  return newValues;
92
101
  },
@@ -116,12 +125,14 @@ export default {
116
125
  return false;
117
126
  }
118
127
 
119
- const fieldValue = getPostMetaFields( select, context )?.[ args.key ]
120
- ?.value;
121
- // Empty string or `false` could be a valid value, so we need to check if the field value is undefined.
122
- if ( fieldValue === undefined ) {
128
+ const metaFields = getPostMetaFields( select, context );
129
+ const hasMatchingMetaField = metaFields.some(
130
+ ( field ) => field.args.key === args.key
131
+ );
132
+ if ( ! hasMatchingMetaField ) {
123
133
  return false;
124
134
  }
135
+
125
136
  // Check that custom fields metabox is not enabled.
126
137
  const areCustomFieldsEnabled =
127
138
  select( editorStore ).getEditorSettings().enableCustomFields;
@@ -143,13 +154,11 @@ export default {
143
154
  },
144
155
  getFieldsList( { select, context } ) {
145
156
  const metaFields = getPostMetaFields( select, context );
146
- if ( ! metaFields ) {
147
- return [];
148
- }
149
- return Object.entries( metaFields ).map( ( [ key, field ] ) => ( {
150
- label: field.label,
151
- type: field.type,
152
- args: { key },
153
- } ) );
157
+ // Remove 'default' property from meta fields.
158
+ return metaFields.map(
159
+ ( { default: defaultProp, ...otherProps } ) => ( {
160
+ ...otherProps,
161
+ } )
162
+ );
154
163
  },
155
164
  };
@@ -0,0 +1,211 @@
1
+ /**
2
+ * Internal dependencies
3
+ */
4
+ import { lock } from '../../lock-unlock';
5
+
6
+ /**
7
+ * Internal dependencies
8
+ */
9
+ import postMetaBindings from '../post-meta';
10
+
11
+ describe( 'post-meta bindings', () => {
12
+ let context, select, selectReturn;
13
+
14
+ beforeAll( () => {
15
+ const getEditedEntityRecord = ( kind, type, id ) => ( {
16
+ meta:
17
+ id === 123
18
+ ? {
19
+ movie_field: 'Test Movie Value',
20
+ _protected_field: 'Protected field value',
21
+ }
22
+ : {},
23
+ } );
24
+
25
+ const getEditorSettings = () => ( {
26
+ enableCustomFields: false,
27
+ } );
28
+
29
+ selectReturn = {
30
+ getEditedEntityRecord,
31
+ getEditorSettings,
32
+ };
33
+
34
+ const getRegisteredPostMeta = () => ( {
35
+ field_without_label_or_default: { type: 'string' },
36
+ field_with_label_only: {
37
+ title: 'Field With Label Only',
38
+ default: '', // If there's no default set, getRegisteredPostMeta() will return an empty string.
39
+ type: 'string',
40
+ },
41
+ movie_field: {
42
+ title: 'Movie Field Label',
43
+ default: 'Movie field default value',
44
+ type: 'string',
45
+ },
46
+ _protected_field: {
47
+ default: 'Protected field default value',
48
+ type: 'string',
49
+ },
50
+ } );
51
+
52
+ lock( selectReturn, { getRegisteredPostMeta } );
53
+
54
+ select = () => selectReturn;
55
+ } );
56
+
57
+ describe( 'when no postId is provided in context', () => {
58
+ beforeAll( () => {
59
+ context = { postType: 'movie' };
60
+ } );
61
+
62
+ describe( 'getValues', () => {
63
+ it( 'should return the meta default value if it is defined', () => {
64
+ const values = postMetaBindings.getValues( {
65
+ select,
66
+ context,
67
+ bindings: {
68
+ content: {
69
+ args: { key: 'movie_field' },
70
+ },
71
+ },
72
+ } );
73
+
74
+ expect( values.content ).toBe( 'Movie field default value' );
75
+ } );
76
+
77
+ it( 'should fall back to the field label if the meta default value is not defined', () => {
78
+ const values = postMetaBindings.getValues( {
79
+ select,
80
+ context,
81
+ bindings: {
82
+ content: {
83
+ args: { key: 'field_with_label_only' },
84
+ },
85
+ },
86
+ } );
87
+
88
+ expect( values.content ).toBe( 'Field With Label Only' );
89
+ } );
90
+
91
+ it( 'should fall back to the field key if the field label is not defined', () => {
92
+ const values = postMetaBindings.getValues( {
93
+ select,
94
+ context,
95
+ bindings: {
96
+ content: {
97
+ args: { key: 'field_without_label_or_default' },
98
+ },
99
+ },
100
+ } );
101
+
102
+ expect( values.content ).toBe(
103
+ 'field_without_label_or_default'
104
+ );
105
+ } );
106
+ } );
107
+
108
+ describe( 'getFieldsList', () => {
109
+ it( 'should return the list of available meta fields, with correct fallbacks for labels, and exclude protected fields', () => {
110
+ const fields = postMetaBindings.getFieldsList( {
111
+ select,
112
+ context,
113
+ } );
114
+
115
+ expect( fields ).toEqual( [
116
+ {
117
+ label: 'field_without_label_or_default',
118
+ type: 'string',
119
+ args: { key: 'field_without_label_or_default' },
120
+ },
121
+ {
122
+ label: 'Field With Label Only',
123
+ type: 'string',
124
+ args: { key: 'field_with_label_only' },
125
+ },
126
+ {
127
+ label: 'Movie Field Label',
128
+ type: 'string',
129
+ args: { key: 'movie_field' },
130
+ },
131
+ ] );
132
+ } );
133
+ } );
134
+ } );
135
+
136
+ describe( 'when postId is provided in context', () => {
137
+ beforeAll( () => {
138
+ context = { postType: 'movie', postId: 123 };
139
+ } );
140
+
141
+ describe( 'getValues', () => {
142
+ it( 'should return the meta value if it is defined', () => {
143
+ const values = postMetaBindings.getValues( {
144
+ select,
145
+ context,
146
+ bindings: {
147
+ content: {
148
+ args: { key: 'movie_field' },
149
+ },
150
+ },
151
+ } );
152
+
153
+ expect( values.content ).toBe( 'Test Movie Value' );
154
+ } );
155
+
156
+ it( 'should fall back to the key when meta field is not accessible', () => {
157
+ const values = postMetaBindings.getValues( {
158
+ select,
159
+ context,
160
+ bindings: {
161
+ content: {
162
+ args: { key: 'inaccessible_field' },
163
+ },
164
+ },
165
+ } );
166
+
167
+ expect( values.content ).toBe( 'inaccessible_field' );
168
+ } );
169
+
170
+ it( 'should fall back to the key when meta field is protected', () => {
171
+ const values = postMetaBindings.getValues( {
172
+ select,
173
+ context,
174
+ bindings: {
175
+ content: {
176
+ args: { key: '_protected_field' },
177
+ },
178
+ },
179
+ } );
180
+
181
+ expect( values.content ).toBe( '_protected_field' );
182
+ } );
183
+ } );
184
+
185
+ describe( 'canUserEditValue', () => {
186
+ beforeAll( () => {
187
+ select = () => ( { ...selectReturn, canUser: () => true } );
188
+ } );
189
+
190
+ it( 'should return false when meta field is not accessible', () => {
191
+ const canUser = postMetaBindings.canUserEditValue( {
192
+ select,
193
+ context,
194
+ args: { key: 'inaccessible_field' },
195
+ } );
196
+
197
+ expect( canUser ).toBe( false );
198
+ } );
199
+
200
+ it( 'should return false when meta field is protected', () => {
201
+ const canUser = postMetaBindings.canUserEditValue( {
202
+ select,
203
+ context,
204
+ args: { key: '_protected_field' },
205
+ } );
206
+
207
+ expect( canUser ).toBe( false );
208
+ } );
209
+ } );
210
+ } );
211
+ } );
@@ -6,7 +6,7 @@ import clsx from 'clsx';
6
6
  * WordPress dependencies
7
7
  */
8
8
  import { __ } from '@wordpress/i18n';
9
- import { useSelect } from '@wordpress/data';
9
+ import { useSelect, useDispatch } from '@wordpress/data';
10
10
  import {
11
11
  __experimentalHStack as HStack,
12
12
  __experimentalVStack as VStack,
@@ -45,6 +45,13 @@ export function AddComment( {
45
45
  };
46
46
  }, [] );
47
47
  const blockElement = useBlockElement( clientId );
48
+ const { toggleBlockSpotlight } = unlock( useDispatch( blockEditorStore ) );
49
+
50
+ const unselectThread = () => {
51
+ setShowCommentBoard( false );
52
+ blockElement?.focus();
53
+ toggleBlockSpotlight( clientId, false );
54
+ };
48
55
 
49
56
  if ( ! showCommentBoard || ! clientId || undefined !== blockCommentId ) {
50
57
  return null;
@@ -61,7 +68,7 @@ export function AddComment( {
61
68
  spacing="3"
62
69
  tabIndex={ 0 }
63
70
  aria-label={ __( 'New note' ) }
64
- role="listitem"
71
+ role="treeitem"
65
72
  ref={ isFloating ? refs.setFloating : undefined }
66
73
  style={
67
74
  isFloating
@@ -73,6 +80,7 @@ export function AddComment( {
73
80
  if ( event.currentTarget.contains( event.relatedTarget ) ) {
74
81
  return;
75
82
  }
83
+ toggleBlockSpotlight( clientId, false );
76
84
  setShowCommentBoard( false );
77
85
  } }
78
86
  >
@@ -85,10 +93,7 @@ export function AddComment( {
85
93
  focusCommentThread( id, commentSidebarRef.current );
86
94
  setShowCommentBoard( false );
87
95
  } }
88
- onCancel={ () => {
89
- setShowCommentBoard( false );
90
- blockElement?.focus();
91
- } }
96
+ onCancel={ unselectThread }
92
97
  reflowComments={ reflowComments }
93
98
  submitButtonText={ __( 'Add note' ) }
94
99
  labelText={ __( 'New note' ) }
@@ -18,38 +18,45 @@ import { store as blockEditorStore } from '@wordpress/block-editor';
18
18
  */
19
19
  import { getAvatarBorderColor } from './utils';
20
20
 
21
- /**
22
- * Render author information for a comment.
23
- *
24
- * @param {Object} props - Component properties.
25
- * @param {string} props.avatar - URL of the author's avatar.
26
- * @param {string} props.name - Name of the author.
27
- * @param {string} props.date - Date of the comment.
28
- * @param {string} props.userId - User ID of the author.
29
- *
30
- * @return {React.ReactNode} The JSX element representing the author's information.
31
- */
32
21
  function CommentAuthorInfo( { avatar, name, date, userId } ) {
22
+ const hasAvatar = !! avatar;
33
23
  const dateSettings = getDateSettings();
34
24
  const {
35
25
  currentUserAvatar,
36
26
  currentUserName,
37
27
  currentUserId,
38
28
  dateFormat = dateSettings.formats.date,
39
- } = useSelect( ( select ) => {
40
- const { getCurrentUser, getEntityRecord } = select( coreStore );
41
- const { getSettings } = select( blockEditorStore );
42
- const userData = getCurrentUser();
43
- const { __experimentalDiscussionSettings } = getSettings();
44
- const defaultAvatar = __experimentalDiscussionSettings?.avatarURL;
45
- const siteSettings = getEntityRecord( 'root', 'site' );
46
- return {
47
- currentUserAvatar: userData?.avatar_urls?.[ 48 ] ?? defaultAvatar,
48
- currentUserName: userData?.name,
49
- currentUserId: userData?.id,
50
- dateFormat: siteSettings?.date_format,
51
- };
52
- }, [] );
29
+ } = useSelect(
30
+ ( select ) => {
31
+ const { canUser, getCurrentUser, getEntityRecord } =
32
+ select( coreStore );
33
+ const siteSettings = canUser( 'read', {
34
+ kind: 'root',
35
+ name: 'site',
36
+ } )
37
+ ? getEntityRecord( 'root', 'site' )
38
+ : undefined;
39
+
40
+ if ( hasAvatar ) {
41
+ return {
42
+ dateFormat: siteSettings?.date_format,
43
+ };
44
+ }
45
+
46
+ const { getSettings } = select( blockEditorStore );
47
+ const { __experimentalDiscussionSettings } = getSettings();
48
+ const defaultAvatar = __experimentalDiscussionSettings?.avatarURL;
49
+ const userData = getCurrentUser();
50
+ return {
51
+ currentUserAvatar:
52
+ userData?.avatar_urls?.[ 48 ] ?? defaultAvatar,
53
+ currentUserName: userData?.name,
54
+ currentUserId: userData?.id,
55
+ dateFormat: siteSettings?.date_format,
56
+ };
57
+ },
58
+ [ hasAvatar ]
59
+ );
53
60
 
54
61
  const commentDate = getDate( date );
55
62
  const commentDateTime = dateI18n( 'c', commentDate );
@@ -87,7 +94,7 @@ function CommentAuthorInfo( { avatar, name, date, userId } ) {
87
94
  { name ?? currentUserName }
88
95
  </span>
89
96
  { date && (
90
- <Tooltip placement="top" text={ tooltipText }>
97
+ <Tooltip text={ tooltipText }>
91
98
  <time
92
99
  dateTime={ commentDateTime }
93
100
  className="editor-collab-sidebar-panel__user-time"