Skip to content

Rich Text Editor: Migrate inline RTE blocks from data-content-udi to data-content-key (closes #23224) - #23259

Merged
kjac merged 2 commits into
v17/devfrom
v17/bugfix/23224-rte-block-inline-migration
Jul 1, 2026
Merged

Rich Text Editor: Migrate inline RTE blocks from data-content-udi to data-content-key (closes #23224)#23259
kjac merged 2 commits into
v17/devfrom
v17/bugfix/23224-rte-block-inline-migration

Conversation

@AndyButland

@AndyButland AndyButland commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Description

Rich Text Editor inline blocks (<umb-rte-block-inline>) created in Umbraco 13 were not being migrated from the legacy data-content-udi="umb://element/{guid}" attribute to the v15+ data-content-key="{dashed-guid}" attribute during upgrade. As a result the block could not be matched to its content on load, so it was pulled out of the text flow and hoisted elsewhere in the document (and dropped on the next save).

The fix is a one-line change to the migration regex in RteBlockHelper.BlockRegex() so it also matches the -inline tag variant.

Fixes #23224

Where the issue came from

This is a regression introduced by #22980 (shipped in 17.5), which fixed the block-level sibling-collapse bug from #22979.

The tell is in the closing-tag part of the regex:

Closing-tag pattern Isolated umb-rte-block-inline
Before #22980 .*<\/umb-rte-block — greedy, no trailing > matched & converted (the <\/umb-rte-block prefix matches inside </umb-rte-block-inline>)
#22980 (17.5) .*?<\/umb-rte-block> — non-greedy, added > 0 matches — </umb-rte-block> cannot match </umb-rte-block-inline>

Making the pattern non-greedy and adding the trailing > correctly fixed the block-level sibling collapse, but the added > meant the closing tag of the inline variant (</umb-rte-block-inline>) no longer matched — so isolated inline blocks stopped being converted. In other words, #22980 traded one failure mode for another on the inline variant.

Note the block metadata (blocks.contentData[].key) has always been normalized correctly; only the markup attribute was left behind, which is why an unmigrated inline block becomes "unplaced" and is hoisted out of position by the editor on load.

The fix

src/Umbraco.Infrastructure/Migrations/Upgrade/V_15_0_0/LocalLinks/RteBlockHelper.cs — add (?:-inline)? to both tag ends of the migration regex (mirrors the runtime parser in RichTextParsingRegexes.cs and the v13 parser):

<umb-rte-block(?:-inline)?\b[^>]*?(?<attribute>data-content-udi)="(?<udi>[^"]+)"[^>]*>.*?<\/umb-rte-block(?:-inline)?>

The non-greedy .*? combined with the two-variant closing tag means the match always stops at the nearest closing tag of either variant, so a mix of inline and block-level siblings never collapse together, and isolated inline blocks still match.

Consistent with #22980, this corrects the existing V_15_0_0 migration rather than adding a new forward migration.

Testing

Automated

Extended RteBlockHelperTests with inline coverage:

  • single isolated inline block converts to a dashed-GUID data-content-key;
  • inline-followed-by-block and block-followed-by-inline siblings each convert independently (asserting no leftover umb://element/ in either);
  • inline block with a class attribute converts, preserving the class;
  • inline block with a malformed UDI is dropped (consistent with block-level behaviour);
  • BlockRegex produces one match per element for a mixed inline/block-level sequence.

Red/green verified: with the original 17.5 regex the 6 new tests fail; with the fix all tests pass.

Manual (v13 → v17 upgrade)

Created a v13 RTE with an inline block (Element Type added as a block on the RTE data type, "Display inline with text" enabled), placed it inline within a paragraph, then upgraded.

Before (v13 stored markup):

<p>Test</p>
<p>abcd <umb-rte-block-inline class="ng-scope ng-isolate-scope" data-content-udi="umb://element/f05b255596584ea88a2f35064097f2a4"><!--Umbraco-Block--></umb-rte-block-inline> abcd</p>
<p>Test</p>

After upgrade — WITHOUT the fix (v17/dev): the block is hoisted to the top of the document and its original inline position is lost:

<p><umb-rte-block-inline data-content-key="f05b2555-9658-4ea8-8a2f-35064097f2a4"></umb-rte-block-inline>Test</p>
<p>Test</p>
<p>abcd abcd</p>
<p>Test</p>

(The data-content-key here is the editor re-emitting the now-unplaced block from contentData at the document start — not the migration doing its job. The stored markup still had data-content-udi at the real position.)

After upgrade — WITH the fix (this branch): the block is migrated in place and stays inline where it belongs:

<p>Test</p>
<p>abcd <umb-rte-block-inline data-content-key="f05b2555-9658-4ea8-8a2f-35064097f2a4"></umb-rte-block-inline> abcd</p>
<p>Test</p>

(The transient ng-scope ng-isolate-scope class and the <!--Umbraco-Block--> placeholder comment are stripped downstream by the Tiptap editor on load/save, not by the migration.)

Copilot AI review requested due to automatic review settings July 1, 2026 13:54
@claude

claude Bot commented Jul 1, 2026

Copy link
Copy Markdown

Claude finished @AndyButland's task in 2m 46s —— View job


PR Review

Target: origin/v17/dev · Based on commit: cc399658

Fixes the regression from #22980 where adding a trailing > to the closing-tag pattern correctly tightened the sibling-block match but inadvertently excluded </umb-rte-block-inline>, so isolated inline blocks stopped being converted. The fix adds (?:-inline)? to both tag ends, mirroring the runtime RichTextParsingRegexes.BlockRegex() pattern.

  • Modified public API: RteBlockHelper.BlockRegex() regex pattern (internal change to match behaviour; public signature unchanged)
  • Other changes: Content that was silently left with data-content-udi after upgrade from v13 will now be correctly migrated. This is a migration-correctness fix with no user-visible behaviour change for already-migrated installs.

Suggestions

  • tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/RteBlockHelperTests.cs:260: There is no ConvertBlockUdisToKeys assertion test for two consecutive inline-only siblings (inline → inline). The existing BlockRegex_MixedBlockAndInlineSiblings_ProducesOneMatchPerBlock test verifies the regex count for inline + block + inline, but there is no parallel to ConvertBlockUdisToKeys_TwoConsecutiveSiblingBlocks that checks the converted output for an inline + inline sequence. Adding one would complete the parity matrix and guard against a future regression that produces one match for the pair instead of two.

Approved with Suggestions for improvement

Good to go — the regex change is minimal, well-reasoned, and consistent with the runtime parser. Tests cover the important cross-variant cases. The suggestion above is strictly additive and does not block merging.


Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes the v13→v15+ upgrade migration for inline RTE blocks (<umb-rte-block-inline>) so legacy data-content-udi="umb://element/{guid}" references are rewritten to data-content-key="{dashed-guid}", preventing inline blocks from becoming “unplaced” and hoisted/dropped in the editor.

Changes:

  • Expanded the v15 upgrade migration regex to match both <umb-rte-block> and <umb-rte-block-inline> opening/closing tags without reintroducing sibling-collapsing.
  • Added unit tests covering inline conversion, mixed inline/block siblings, class attribute preservation, malformed UDI behavior, and match-count behavior.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/RteBlockHelperTests.cs Adds inline and mixed-sibling coverage to prevent regressions in UDI→key migration.
src/Umbraco.Infrastructure/Migrations/Upgrade/V_15_0_0/LocalLinks/RteBlockHelper.cs Updates the migration regex to match both block and inline RTE block tag variants.

@claude claude Bot added the area/backend label Jul 1, 2026
Completes the ConvertBlockUdisToKeys parity matrix: there was an
inline+block and block+inline assertion but no inline+inline one,
leaving a gap where a future regression could collapse a consecutive
inline pair into a single match.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@AndyButland AndyButland changed the title RTE: Migrate inline RTE blocks from data-content-udi to data-content-key (closes #23224) Rich Text Editor: Migrate inline RTE blocks from data-content-udi to data-content-key (closes #23224) Jul 1, 2026

@kjac kjac left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Works like a charm 👏

Upgrade from V13 to V17 correctly handles both "regular" blocks and inline blocks:

Image

@kjac
kjac merged commit 51cc6b3 into v17/dev Jul 1, 2026
27 checks passed
@kjac
kjac deleted the v17/bugfix/23224-rte-block-inline-migration branch July 1, 2026 15:23
AndyButland added a commit that referenced this pull request Jul 2, 2026
…data-content-key (closes #23224) (#23259)

* Fix regression in migration for umb-rte-block-inline blocks.

* Add inline-inline sibling parity test for RTE block migration

Completes the ConvertBlockUdisToKeys parity matrix: there was an
inline+block and block+inline assertion but no inline+inline one,
leaving a gap where a future regression could collapse a consecutive
inline pair into a single match.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants