@wordpress/editor 14.50.0 → 14.51.1-next.v.202607070741.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 (197) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/build/components/collab-sidebar/format.cjs +83 -0
  3. package/build/components/collab-sidebar/format.cjs.map +7 -0
  4. package/build/components/collab-sidebar/hooks.cjs +117 -16
  5. package/build/components/collab-sidebar/hooks.cjs.map +2 -2
  6. package/build/components/collab-sidebar/index.cjs +18 -2
  7. package/build/components/collab-sidebar/index.cjs.map +2 -2
  8. package/build/components/collab-sidebar/note-highlight-styles.cjs +67 -0
  9. package/build/components/collab-sidebar/note-highlight-styles.cjs.map +7 -0
  10. package/build/components/collab-sidebar/utils.cjs +139 -0
  11. package/build/components/collab-sidebar/utils.cjs.map +2 -2
  12. package/build/components/collaborators-presence/use-collaborator-notifications.cjs +55 -30
  13. package/build/components/collaborators-presence/use-collaborator-notifications.cjs.map +2 -2
  14. package/build/components/commands/index.cjs +99 -3
  15. package/build/components/commands/index.cjs.map +2 -2
  16. package/build/components/document-bar/index.cjs +28 -34
  17. package/build/components/document-bar/index.cjs.map +2 -2
  18. package/build/components/entities-saved-states/index.cjs +1 -4
  19. package/build/components/entities-saved-states/index.cjs.map +2 -2
  20. package/build/components/media-categories/index.cjs +92 -6
  21. package/build/components/media-categories/index.cjs.map +2 -2
  22. package/build/components/page-attributes/parent.cjs +0 -1
  23. package/build/components/page-attributes/parent.cjs.map +2 -2
  24. package/build/components/post-author/combobox.cjs +0 -1
  25. package/build/components/post-author/combobox.cjs.map +2 -2
  26. package/build/components/post-author/select.cjs +0 -1
  27. package/build/components/post-author/select.cjs.map +2 -2
  28. package/build/components/post-preview-button/index.cjs +29 -2
  29. package/build/components/post-preview-button/index.cjs.map +2 -2
  30. package/build/components/post-publish-panel/maybe-upload-media.cjs +15 -26
  31. package/build/components/post-publish-panel/maybe-upload-media.cjs.map +3 -3
  32. package/build/components/post-revisions-preview/revisions-slider.cjs +3 -1
  33. package/build/components/post-revisions-preview/revisions-slider.cjs.map +2 -2
  34. package/build/components/post-taxonomies/flat-term-selector.cjs +0 -1
  35. package/build/components/post-taxonomies/flat-term-selector.cjs.map +2 -2
  36. package/build/components/post-taxonomies/hierarchical-term-selector.cjs +0 -2
  37. package/build/components/post-taxonomies/hierarchical-term-selector.cjs.map +2 -2
  38. package/build/components/post-template/classic-theme.cjs +0 -1
  39. package/build/components/post-template/classic-theme.cjs.map +2 -2
  40. package/build/components/preferences-modal/index.cjs +54 -28
  41. package/build/components/preferences-modal/index.cjs.map +2 -2
  42. package/build/components/preview-dropdown/index.cjs +3 -3
  43. package/build/components/preview-dropdown/index.cjs.map +2 -2
  44. package/build/components/provider/use-block-editor-settings.cjs +23 -3
  45. package/build/components/provider/use-block-editor-settings.cjs.map +3 -3
  46. package/build/components/sidebar/dataform-post-summary.cjs +5 -1
  47. package/build/components/sidebar/dataform-post-summary.cjs.map +3 -3
  48. package/build/components/sidebar/index.cjs +29 -38
  49. package/build/components/sidebar/index.cjs.map +3 -3
  50. package/build/components/sidebar/post-summary.cjs +0 -13
  51. package/build/components/sidebar/post-summary.cjs.map +3 -3
  52. package/build/dataviews/store/private-actions.cjs +24 -19
  53. package/build/dataviews/store/private-actions.cjs.map +2 -2
  54. package/build/store/private-actions.cjs +5 -2
  55. package/build/store/private-actions.cjs.map +2 -2
  56. package/build/utils/media-sideload-from-url/index.cjs +63 -0
  57. package/build/utils/media-sideload-from-url/index.cjs.map +7 -0
  58. package/build-module/components/collab-sidebar/format.mjs +57 -0
  59. package/build-module/components/collab-sidebar/format.mjs.map +7 -0
  60. package/build-module/components/collab-sidebar/hooks.mjs +121 -16
  61. package/build-module/components/collab-sidebar/hooks.mjs.map +2 -2
  62. package/build-module/components/collab-sidebar/index.mjs +21 -5
  63. package/build-module/components/collab-sidebar/index.mjs.map +2 -2
  64. package/build-module/components/collab-sidebar/note-highlight-styles.mjs +41 -0
  65. package/build-module/components/collab-sidebar/note-highlight-styles.mjs.map +7 -0
  66. package/build-module/components/collab-sidebar/utils.mjs +133 -0
  67. package/build-module/components/collab-sidebar/utils.mjs.map +2 -2
  68. package/build-module/components/collaborators-presence/use-collaborator-notifications.mjs +55 -30
  69. package/build-module/components/collaborators-presence/use-collaborator-notifications.mjs.map +2 -2
  70. package/build-module/components/commands/index.mjs +99 -3
  71. package/build-module/components/commands/index.mjs.map +2 -2
  72. package/build-module/components/document-bar/index.mjs +28 -34
  73. package/build-module/components/document-bar/index.mjs.map +2 -2
  74. package/build-module/components/entities-saved-states/index.mjs +1 -4
  75. package/build-module/components/entities-saved-states/index.mjs.map +2 -2
  76. package/build-module/components/media-categories/index.mjs +93 -7
  77. package/build-module/components/media-categories/index.mjs.map +2 -2
  78. package/build-module/components/page-attributes/parent.mjs +0 -1
  79. package/build-module/components/page-attributes/parent.mjs.map +2 -2
  80. package/build-module/components/post-author/combobox.mjs +0 -1
  81. package/build-module/components/post-author/combobox.mjs.map +2 -2
  82. package/build-module/components/post-author/select.mjs +0 -1
  83. package/build-module/components/post-author/select.mjs.map +2 -2
  84. package/build-module/components/post-preview-button/index.mjs +29 -2
  85. package/build-module/components/post-preview-button/index.mjs.map +2 -2
  86. package/build-module/components/post-publish-panel/maybe-upload-media.mjs +19 -27
  87. package/build-module/components/post-publish-panel/maybe-upload-media.mjs.map +2 -2
  88. package/build-module/components/post-revisions-preview/revisions-slider.mjs +3 -1
  89. package/build-module/components/post-revisions-preview/revisions-slider.mjs.map +2 -2
  90. package/build-module/components/post-taxonomies/flat-term-selector.mjs +0 -1
  91. package/build-module/components/post-taxonomies/flat-term-selector.mjs.map +2 -2
  92. package/build-module/components/post-taxonomies/hierarchical-term-selector.mjs +0 -2
  93. package/build-module/components/post-taxonomies/hierarchical-term-selector.mjs.map +2 -2
  94. package/build-module/components/post-template/classic-theme.mjs +0 -1
  95. package/build-module/components/post-template/classic-theme.mjs.map +2 -2
  96. package/build-module/components/preferences-modal/index.mjs +54 -28
  97. package/build-module/components/preferences-modal/index.mjs.map +2 -2
  98. package/build-module/components/preview-dropdown/index.mjs +3 -3
  99. package/build-module/components/preview-dropdown/index.mjs.map +2 -2
  100. package/build-module/components/provider/use-block-editor-settings.mjs +23 -3
  101. package/build-module/components/provider/use-block-editor-settings.mjs.map +2 -2
  102. package/build-module/components/sidebar/dataform-post-summary.mjs +6 -2
  103. package/build-module/components/sidebar/dataform-post-summary.mjs.map +2 -2
  104. package/build-module/components/sidebar/index.mjs +29 -42
  105. package/build-module/components/sidebar/index.mjs.map +2 -2
  106. package/build-module/components/sidebar/post-summary.mjs +0 -13
  107. package/build-module/components/sidebar/post-summary.mjs.map +2 -2
  108. package/build-module/dataviews/store/private-actions.mjs +26 -19
  109. package/build-module/dataviews/store/private-actions.mjs.map +2 -2
  110. package/build-module/store/private-actions.mjs +5 -2
  111. package/build-module/store/private-actions.mjs.map +2 -2
  112. package/build-module/utils/media-sideload-from-url/index.mjs +32 -0
  113. package/build-module/utils/media-sideload-from-url/index.mjs.map +7 -0
  114. package/build-style/style-rtl.css +65 -33
  115. package/build-style/style.css +65 -33
  116. package/build-types/components/collab-sidebar/format.d.ts +17 -0
  117. package/build-types/components/collab-sidebar/format.d.ts.map +1 -0
  118. package/build-types/components/collab-sidebar/hooks.d.ts.map +1 -1
  119. package/build-types/components/collab-sidebar/index.d.ts.map +1 -1
  120. package/build-types/components/collab-sidebar/note-highlight-styles.d.ts +31 -0
  121. package/build-types/components/collab-sidebar/note-highlight-styles.d.ts.map +1 -0
  122. package/build-types/components/collab-sidebar/utils.d.ts +82 -2
  123. package/build-types/components/collab-sidebar/utils.d.ts.map +1 -1
  124. package/build-types/components/collaborators-presence/use-collaborator-notifications.d.ts.map +1 -1
  125. package/build-types/components/commands/index.d.ts.map +1 -1
  126. package/build-types/components/document-bar/index.d.ts.map +1 -1
  127. package/build-types/components/entities-saved-states/index.d.ts.map +1 -1
  128. package/build-types/components/media-categories/index.d.ts +12 -3
  129. package/build-types/components/media-categories/index.d.ts.map +1 -1
  130. package/build-types/components/page-attributes/parent.d.ts.map +1 -1
  131. package/build-types/components/post-author/combobox.d.ts.map +1 -1
  132. package/build-types/components/post-author/select.d.ts.map +1 -1
  133. package/build-types/components/post-preview-button/index.d.ts.map +1 -1
  134. package/build-types/components/post-publish-panel/maybe-upload-media.d.ts.map +1 -1
  135. package/build-types/components/post-revisions-preview/revisions-slider.d.ts.map +1 -1
  136. package/build-types/components/post-taxonomies/flat-term-selector.d.ts.map +1 -1
  137. package/build-types/components/post-taxonomies/hierarchical-term-selector.d.ts.map +1 -1
  138. package/build-types/components/post-template/classic-theme.d.ts.map +1 -1
  139. package/build-types/components/provider/use-block-editor-settings.d.ts.map +1 -1
  140. package/build-types/components/sidebar/dataform-post-summary.d.ts.map +1 -1
  141. package/build-types/components/sidebar/index.d.ts.map +1 -1
  142. package/build-types/components/sidebar/post-summary.d.ts.map +1 -1
  143. package/build-types/dataviews/store/private-actions.d.ts.map +1 -1
  144. package/build-types/store/private-actions.d.ts.map +1 -1
  145. package/build-types/store/selectors.d.ts.map +1 -1
  146. package/build-types/utils/get-template-part-icon.d.ts.map +1 -1
  147. package/build-types/utils/media-sideload-from-url/index.d.ts +18 -0
  148. package/build-types/utils/media-sideload-from-url/index.d.ts.map +1 -0
  149. package/package.json +48 -46
  150. package/src/components/collab-sidebar/format.js +72 -0
  151. package/src/components/collab-sidebar/hooks.js +203 -22
  152. package/src/components/collab-sidebar/index.js +19 -5
  153. package/src/components/collab-sidebar/note-highlight-styles.js +83 -0
  154. package/src/components/collab-sidebar/style.scss +9 -0
  155. package/src/components/collab-sidebar/test/note-highlight-styles.js +106 -0
  156. package/src/components/collab-sidebar/test/utils.js +471 -0
  157. package/src/components/collab-sidebar/utils.js +225 -2
  158. package/src/components/collaborators-presence/test/use-collaborator-notifications.ts +265 -262
  159. package/src/components/collaborators-presence/use-collaborator-notifications.ts +70 -34
  160. package/src/components/commands/index.js +142 -0
  161. package/src/components/document-bar/index.js +0 -9
  162. package/src/components/document-bar/style.scss +0 -17
  163. package/src/components/entities-saved-states/index.js +1 -5
  164. package/src/components/header/style.scss +7 -3
  165. package/src/components/media-categories/index.js +160 -6
  166. package/src/components/media-categories/test/index.js +202 -0
  167. package/src/components/page-attributes/parent.js +0 -1
  168. package/src/components/post-author/combobox.js +0 -1
  169. package/src/components/post-author/select.js +0 -1
  170. package/src/components/post-preview-button/index.js +68 -2
  171. package/src/components/post-publish-panel/maybe-upload-media.js +25 -30
  172. package/src/components/post-publish-panel/style.scss +1 -1
  173. package/src/components/post-publish-panel/test/__snapshots__/index.js.snap +11 -127
  174. package/src/components/post-revisions-preview/revisions-slider.js +4 -1
  175. package/src/components/post-revisions-preview/test/block-diff.js +2 -2
  176. package/src/components/post-taxonomies/flat-term-selector.js +0 -1
  177. package/src/components/post-taxonomies/hierarchical-term-selector.js +0 -2
  178. package/src/components/post-template/classic-theme.js +0 -1
  179. package/src/components/preferences-modal/index.js +40 -22
  180. package/src/components/preferences-modal/test/index.js +83 -0
  181. package/src/components/preview-dropdown/index.js +3 -3
  182. package/src/components/provider/use-block-editor-settings.js +42 -1
  183. package/src/components/sidebar/dataform-post-summary.js +13 -1
  184. package/src/components/sidebar/index.js +32 -47
  185. package/src/components/sidebar/post-summary.js +0 -17
  186. package/src/dataviews/store/private-actions.ts +30 -26
  187. package/src/store/private-actions.js +6 -2
  188. package/src/utils/media-sideload-from-url/index.js +54 -0
  189. package/src/utils/media-sideload-from-url/test/index.js +162 -0
  190. package/build/components/post-publish-panel/media-util.cjs +0 -72
  191. package/build/components/post-publish-panel/media-util.cjs.map +0 -7
  192. package/build-module/components/post-publish-panel/media-util.mjs +0 -46
  193. package/build-module/components/post-publish-panel/media-util.mjs.map +0 -7
  194. package/build-types/components/post-publish-panel/media-util.d.ts +0 -20
  195. package/build-types/components/post-publish-panel/media-util.d.ts.map +0 -1
  196. package/src/components/post-publish-panel/media-util.js +0 -87
  197. package/src/components/post-publish-panel/test/media-util.js +0 -118
@@ -2,9 +2,10 @@
2
2
  * WordPress dependencies
3
3
  */
4
4
  import { _x } from '@wordpress/i18n';
5
+ import { create, RichTextData } from '@wordpress/rich-text';
5
6
 
6
7
  /**
7
- * Sanitizes a note string by removing non-printable ASCII characters.
8
+ * Sanitizes a note string by trimming leading and trailing whitespace.
8
9
  *
9
10
  * @param {string} str - The note string to sanitize.
10
11
  * @return {string} - The sanitized note string.
@@ -34,8 +35,11 @@ const AVATAR_BORDER_COLORS = [
34
35
  /**
35
36
  * Gets the border color for an avatar based on the user ID.
36
37
  *
38
+ * Always returns a 6-digit `#RRGGBB` hex string; callers (e.g. the highlight
39
+ * styles) rely on this format to append alpha suffixes.
40
+ *
37
41
  * @param {number} userId - The user ID.
38
- * @return {string} - The border color.
42
+ * @return {string} - The border color as a `#RRGGBB` hex string.
39
43
  */
40
44
  export function getAvatarBorderColor( userId ) {
41
45
  return AVATAR_BORDER_COLORS[ userId % AVATAR_BORDER_COLORS.length ];
@@ -129,6 +133,225 @@ export function addNoteIdToMetadata( metadata, noteId ) {
129
133
  return { ...metadata, noteId: [ ...ids ] };
130
134
  }
131
135
 
136
+ const NOTE_FORMAT_TYPE = 'core/note';
137
+
138
+ /**
139
+ * Search a rich-text value for a `core/note` marker matching `noteId` and
140
+ * return its character range. Used to derive an inline note's anchor from
141
+ * the in-content marker (resilient to edits) rather than stale offset meta.
142
+ *
143
+ * @param {*} value Block attribute value (RichTextData, string, or other).
144
+ * @param {number|string} noteId Note id to search for.
145
+ * @return {?{start: number, end: number}} Range or null when no marker is found.
146
+ */
147
+ export function findNoteRange( value, noteId ) {
148
+ if ( noteId === undefined || noteId === null ) {
149
+ return null;
150
+ }
151
+ let html = null;
152
+ if ( value instanceof RichTextData ) {
153
+ html = value.toHTMLString();
154
+ } else if ( typeof value === 'string' ) {
155
+ html = value;
156
+ }
157
+ if ( ! html || html.indexOf( 'wp-note' ) === -1 ) {
158
+ return null;
159
+ }
160
+ const target = String( noteId );
161
+ const record = create( { html } );
162
+ const formats = record.formats;
163
+ let start = -1;
164
+ for ( let i = 0; i < formats.length; i++ ) {
165
+ const stack = formats[ i ];
166
+ const hit = stack?.find(
167
+ ( f ) =>
168
+ f.type === NOTE_FORMAT_TYPE &&
169
+ f.attributes &&
170
+ f.attributes[ 'data-id' ] === target
171
+ );
172
+ if ( hit ) {
173
+ if ( start === -1 ) {
174
+ start = i;
175
+ }
176
+ } else if ( start !== -1 ) {
177
+ return { start, end: i };
178
+ }
179
+ }
180
+ if ( start !== -1 ) {
181
+ return { start, end: formats.length };
182
+ }
183
+ return null;
184
+ }
185
+
186
+ /**
187
+ * Locate a note's in-content `core/note` marker across all of a block's
188
+ * attributes. The marker (carrying `data-id`) is the single source of truth for
189
+ * an inline note's anchor: a note is inline iff a marker with its id exists in
190
+ * the block, and the attribute that holds it is discovered here rather than
191
+ * stored separately. Returns the matching attribute key and the marker range.
192
+ *
193
+ * @param {?Object} attributes Block attributes, or null/undefined when unloaded.
194
+ * @param {number|string} noteId Note id to search for.
195
+ * @return {?{attributeKey: string, start: number, end: number}} Anchor or null when no marker is found.
196
+ */
197
+ export function findNoteInBlock( attributes, noteId ) {
198
+ if ( ! attributes ) {
199
+ return null;
200
+ }
201
+ for ( const attributeKey of Object.keys( attributes ) ) {
202
+ const range = findNoteRange( attributes[ attributeKey ], noteId );
203
+ if ( range ) {
204
+ return { attributeKey, start: range.start, end: range.end };
205
+ }
206
+ }
207
+ return null;
208
+ }
209
+
210
+ // Sentinel that sorts a block-level (whole-block) note before any inline note
211
+ // within the same block. Negative so any real character offset (>= 0) ranks
212
+ // after it. Number.NEGATIVE_INFINITY would work too; -1 is enough and keeps
213
+ // the diff arithmetic in safe integers.
214
+ export const BLOCK_LEVEL_NOTE_START = -1;
215
+
216
+ /**
217
+ * Resolve an inline note's character offset in its block so threads can be
218
+ * sorted by reading order. A note is inline iff an in-content `core/note`
219
+ * marker carries its id; block-level notes (no marker) sort first within their
220
+ * block via a sentinel.
221
+ *
222
+ * @param {Object} thread Materialized thread record (with `.id`).
223
+ * @param {?Object} attributes Block attributes for the thread's block.
224
+ * @return {number} Marker start offset, or `BLOCK_LEVEL_NOTE_START` when there is no inline anchor.
225
+ */
226
+ export function getInlineMarkerStart( thread, attributes ) {
227
+ const found = findNoteInBlock( attributes, thread?.id );
228
+ return found ? found.start : BLOCK_LEVEL_NOTE_START;
229
+ }
230
+
231
+ /**
232
+ * Apply a `core/note` marker across `[start, end)` without removing notes
233
+ * already present in that range.
234
+ *
235
+ * Rich-text's `applyFormat` strips any existing format of the same type before
236
+ * applying, so two `core/note` markers can't coexist - a note drawn over an
237
+ * existing one would wipe it in the overlap. This keeps every overlapping note
238
+ * and orders the markers outermost-first by span, so a note fully contained in
239
+ * another nests inside it (`<mark><mark>…</mark></mark>`). Crossing (partial)
240
+ * overlaps can't nest in HTML and serialize as split runs, but each note keeps
241
+ * its full range. The returned record is not normalised; callers should
242
+ * round-trip it (e.g. through `RichTextData`) before storing.
243
+ *
244
+ * @param {Object} record A rich-text record (`{ text, formats, … }`).
245
+ * @param {Object} format The `core/note` format to add (`{ type, attributes }`).
246
+ * @param {number} start Range start (inclusive).
247
+ * @param {number} end Range end (exclusive).
248
+ * @return {Object} A new record with the note applied.
249
+ */
250
+ export function applyNoteFormat( record, format, start, end ) {
251
+ const formats = record.formats.slice();
252
+ for ( let i = start; i < end; i++ ) {
253
+ const stack = formats[ i ] ? formats[ i ].slice() : [];
254
+ stack.push( format );
255
+ formats[ i ] = stack;
256
+ }
257
+
258
+ // Measure each note's full span so containment can order the markers.
259
+ const spans = new Map();
260
+ for ( let i = 0; i < formats.length; i++ ) {
261
+ const stack = formats[ i ];
262
+ if ( ! stack ) {
263
+ continue;
264
+ }
265
+ for ( const fmt of stack ) {
266
+ if ( fmt.type !== NOTE_FORMAT_TYPE ) {
267
+ continue;
268
+ }
269
+ const id = fmt.attributes?.[ 'data-id' ];
270
+ const span = spans.get( id );
271
+ if ( span ) {
272
+ span.end = i;
273
+ } else {
274
+ spans.set( id, { start: i, end: i } );
275
+ }
276
+ }
277
+ }
278
+ const sizeOf = ( id ) => {
279
+ const span = spans.get( id );
280
+ return span ? span.end - span.start : 0;
281
+ };
282
+
283
+ // Order markers outermost-first (widest span) so `toTree` nests them rather
284
+ // than splitting an outer note around an inner one. Notes sort ahead of
285
+ // other formats so a note wraps the formatted text it spans.
286
+ for ( let i = 0; i < formats.length; i++ ) {
287
+ const stack = formats[ i ];
288
+ if ( ! stack || stack.length < 2 ) {
289
+ continue;
290
+ }
291
+ const notes = stack.filter( ( fmt ) => fmt.type === NOTE_FORMAT_TYPE );
292
+ if ( notes.length === 0 ) {
293
+ continue;
294
+ }
295
+ if ( notes.length > 1 ) {
296
+ notes.sort(
297
+ ( a, b ) =>
298
+ sizeOf( b.attributes?.[ 'data-id' ] ) -
299
+ sizeOf( a.attributes?.[ 'data-id' ] )
300
+ );
301
+ }
302
+ const others = stack.filter( ( fmt ) => fmt.type !== NOTE_FORMAT_TYPE );
303
+ formats[ i ] = [ ...notes, ...others ];
304
+ }
305
+
306
+ return { ...record, formats };
307
+ }
308
+
309
+ /**
310
+ * Remove a single note's `core/note` marker from a rich-text value, leaving any
311
+ * other notes nested or overlapping with it intact. Used when a note is deleted
312
+ * or resolved so its highlight does not linger in the content.
313
+ *
314
+ * Rich-text's `removeFormat` strips every `core/note` marker in a range, so it
315
+ * would wipe co-located notes; this filters by `data-id` to drop only the target
316
+ * marker.
317
+ *
318
+ * @param {*} value Block attribute value (RichTextData or other).
319
+ * @param {number|string} noteId Note id whose marker should be removed.
320
+ * @return {?RichTextData} A new value with the marker removed, or null when the
321
+ * attribute isn't rich text or carries no such marker.
322
+ */
323
+ export function removeNoteFormat( value, noteId ) {
324
+ if ( ! ( value instanceof RichTextData ) ) {
325
+ return null;
326
+ }
327
+ const target = String( noteId );
328
+ const record = create( { html: value.toHTMLString() } );
329
+ let changed = false;
330
+ const formats = record.formats.map( ( stack ) => {
331
+ if ( ! stack ) {
332
+ return stack;
333
+ }
334
+ const filtered = stack.filter(
335
+ ( format ) =>
336
+ ! (
337
+ format.type === NOTE_FORMAT_TYPE &&
338
+ format.attributes?.[ 'data-id' ] === target
339
+ )
340
+ );
341
+ if ( filtered.length === stack.length ) {
342
+ return stack;
343
+ }
344
+ changed = true;
345
+ return filtered.length ? filtered : undefined;
346
+ } );
347
+ // Round-trip through HTML so the stored value matches a fresh reload.
348
+ return changed
349
+ ? RichTextData.fromHTMLString(
350
+ new RichTextData( { ...record, formats } ).toHTMLString()
351
+ )
352
+ : null;
353
+ }
354
+
132
355
  /**
133
356
  * Picks the most relevant thread from a list: first unresolved, else first.
134
357
  *