on reload. * * Only note markers are unwrapped: {@see WP_HTML_Tag_Processor::has_class()} * matches the `wp-note` class by exact token, so a `` a user or plugin * added (e.g. a `core/text-color` highlight, or an unrelated `wp-note-foo` * class) is never flagged and survives byte-for-byte with all of its attributes * intact. A naive regex would be wrong here: a `\bwp-note\b` word boundary also * matches `wp-note-foo`, which is why the class check goes through the HTML API * instead. * * The HTML API has no public token-removal method yet, so an anonymous * {@see WP_HTML_Tag_Processor} subclass unwraps each note `` and its * matching closer directly on the parsed token stream. Walking tokens - rather * than matching `` with a regex - means a ``-looking sequence inside * a comment or attribute value can never be mistaken for a real tag, and a * nesting stack keeps each note opener paired with its own closer so overlapping * notes and any user highlight `` left intact still resolve correctly. * * The low-level {@see WP_HTML_Tag_Processor} is used deliberately, rather than * the tree-building {@see WP_HTML_Processor}. Note markers live in user-editable * content, so the markup is not guaranteed to be well formed. On certain * ill-formed nesting the tree builder aborts, which would leave note markers - * and their metadata - in the rendered output. Scanning tokens instead removes * every `wp-note` marker it encounters and degrades gracefully: an unbalanced or * stray tag is left exactly as it was rather than corrupting surrounding markup. * * @since 7.1.0 * * @param string $block_content Rendered block HTML. * @return string Block HTML with `wp-note` markers unwrapped. */ function wp_strip_inline_note_markers( $block_content ) { if ( ! str_contains( $block_content, 'wp-note' ) ) { return $block_content; } /* * Anonymous subclass exposing token removal, which WP_HTML_Tag_Processor * does not provide publicly yet. Removing the current token via its bookmark * span unwraps the `` (opener or closer) while keeping the text it * wraps. */ $processor = new class( $block_content ) extends WP_HTML_Tag_Processor { /** * Removes the current token, keeping any text it wraps. */ public function remove_token(): void { // Always called after next_tag() returned true, so the bookmark is set. $this->set_bookmark( 'here' ); $span = $this->bookmarks['here']; $this->lexical_updates[] = new WP_HTML_Text_Replacement( $span->start, $span->length, '' ); } }; /* * Walk every ``, tracking note nesting on a stack so each note opener * pairs with its own closer, and unwrap only the note markers. */ $mark_stack = array(); $query = array( 'tag_name' => 'MARK', 'tag_closers' => 'visit', ); while ( $processor->next_tag( $query ) ) { if ( $processor->is_tag_closer() ) { $is_note = array_pop( $mark_stack ); } else { $is_note = $processor->has_class( 'wp-note' ); $mark_stack[] = $is_note; } if ( true === $is_note ) { $processor->remove_token(); } } return $processor->get_updated_html(); }