diff --git a/src/Umbraco.Infrastructure/DeliveryApi/ApiRichTextElementParser.cs b/src/Umbraco.Infrastructure/DeliveryApi/ApiRichTextElementParser.cs index b54976ff4067..02a3d323c7cb 100644 --- a/src/Umbraco.Infrastructure/DeliveryApi/ApiRichTextElementParser.cs +++ b/src/Umbraco.Infrastructure/DeliveryApi/ApiRichTextElementParser.cs @@ -143,8 +143,9 @@ private void ReplaceLocalLinks(IPublishedContentCache contentCache, IPublishedMe mediaCache, href, type, - route => + (route, contentId) => { + attributes["destination-id"] = contentId; attributes["route"] = route; attributes.Remove("href"); }, diff --git a/src/Umbraco.Infrastructure/DeliveryApi/ApiRichTextMarkupParser.cs b/src/Umbraco.Infrastructure/DeliveryApi/ApiRichTextMarkupParser.cs index 3712a4b6c5cc..95dbd8a1c7d9 100644 --- a/src/Umbraco.Infrastructure/DeliveryApi/ApiRichTextMarkupParser.cs +++ b/src/Umbraco.Infrastructure/DeliveryApi/ApiRichTextMarkupParser.cs @@ -2,6 +2,7 @@ using Microsoft.Extensions.Logging; using Umbraco.Cms.Core; using Umbraco.Cms.Core.DeliveryApi; +using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.PublishedCache; using Umbraco.Extensions; @@ -58,17 +59,20 @@ private void ReplaceLocalLinks(HtmlDocument doc, IPublishedContentCache contentC mediaCache, link.GetAttributeValue("href", string.Empty), link.GetAttributeValue("type", "unknown"), - route => + (route, contentId) => { link.SetAttributeValue("href", $"{route.Path}{route.QueryString}"); + link.SetAttributeValue("data-destination-id", contentId.ToString("D")); link.SetAttributeValue("data-start-item-path", route.StartItem.Path); link.SetAttributeValue("data-start-item-id", route.StartItem.Id.ToString("D")); link.Attributes["type"]?.Remove(); + link.SetAttributeValue("data-link-type", nameof(LinkType.Content)); }, url => { link.SetAttributeValue("href", url); link.Attributes["type"]?.Remove(); + link.SetAttributeValue("data-link-type", nameof(LinkType.Media)); }, () => { diff --git a/src/Umbraco.Infrastructure/DeliveryApi/ApiRichTextParserBase.cs b/src/Umbraco.Infrastructure/DeliveryApi/ApiRichTextParserBase.cs index d1cf1b5b3ae8..64a55ba3d3d3 100644 --- a/src/Umbraco.Infrastructure/DeliveryApi/ApiRichTextParserBase.cs +++ b/src/Umbraco.Infrastructure/DeliveryApi/ApiRichTextParserBase.cs @@ -21,7 +21,7 @@ protected ApiRichTextParserBase(IApiContentRouteBuilder apiContentRouteBuilder, _apiMediaUrlProvider = apiMediaUrlProvider; } - protected void ReplaceLocalLinks(IPublishedContentCache contentCache, IPublishedMediaCache mediaCache, string href, string type, Action handleContentRoute, Action handleMediaUrl, Action handleInvalidLink) + protected void ReplaceLocalLinks(IPublishedContentCache contentCache, IPublishedMediaCache mediaCache, string href, string type, Action handleContentRoute, Action handleMediaUrl, Action handleInvalidLink) { ReplaceStatus replaceAttempt = ReplaceLocalLink(contentCache, mediaCache, href, type, handleContentRoute, handleMediaUrl); if (replaceAttempt == ReplaceStatus.Success) @@ -35,7 +35,7 @@ protected void ReplaceLocalLinks(IPublishedContentCache contentCache, IPublished } } - private ReplaceStatus ReplaceLocalLink(IPublishedContentCache contentCache, IPublishedMediaCache mediaCache, string href, string type, Action handleContentRoute, Action handleMediaUrl) + private ReplaceStatus ReplaceLocalLink(IPublishedContentCache contentCache, IPublishedMediaCache mediaCache, string href, string type, Action handleContentRoute, Action handleMediaUrl) { Match match = LocalLinkRegex().Match(href); if (match.Success is false) @@ -60,7 +60,7 @@ private ReplaceStatus ReplaceLocalLink(IPublishedContentCache contentCache, IPub if (route != null) { route.QueryString = match.Groups["query"].Value.NullOrWhiteSpaceAsNull(); - handleContentRoute(route); + handleContentRoute(route, guid); return ReplaceStatus.Success; } @@ -79,7 +79,7 @@ private ReplaceStatus ReplaceLocalLink(IPublishedContentCache contentCache, IPub return ReplaceStatus.InvalidEntityType; } - private ReplaceStatus ReplaceLegacyLocalLink(IPublishedContentCache contentCache, IPublishedMediaCache mediaCache, string href, Action handleContentRoute, Action handleMediaUrl) + private ReplaceStatus ReplaceLegacyLocalLink(IPublishedContentCache contentCache, IPublishedMediaCache mediaCache, string href, Action handleContentRoute, Action handleMediaUrl) { Match match = LegacyLocalLinkRegex().Match(href); if (match.Success is false) @@ -108,7 +108,7 @@ private ReplaceStatus ReplaceLegacyLocalLink(IPublishedContentCache contentCache if (route != null) { route.QueryString = match.Groups["query"].Value.NullOrWhiteSpaceAsNull(); - handleContentRoute(route); + handleContentRoute(route, guidUdi.Guid); return ReplaceStatus.Success; } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/RichTextParserTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/RichTextParserTests.cs index 2acab6caced3..de634f3df36d 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/RichTextParserTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/RichTextParserTests.cs @@ -3,6 +3,7 @@ using NUnit.Framework; using Umbraco.Cms.Core; using Umbraco.Cms.Core.DeliveryApi; +using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Blocks; using Umbraco.Cms.Core.Models.DeliveryApi; using Umbraco.Cms.Core.Models.PublishedContent; @@ -141,9 +142,12 @@ public void ParseElement_CanParseContentLink(string? postfix) var link = element.Elements.OfType().Single().Elements.Single() as RichTextGenericElement; Assert.IsNotNull(link); Assert.AreEqual("a", link.Tag); - Assert.AreEqual(1, link.Attributes.Count); - Assert.AreEqual("route", link.Attributes.First().Key); - var route = link.Attributes.First().Value as IApiContentRoute; + Assert.AreEqual(2, link.Attributes.Count); + Assert.IsNotNull(link.Attributes["route"]); + Assert.IsNotNull(link.Attributes["destination-id"]); + var id = (Guid)link.Attributes["destination-id"]; + Assert.AreEqual(_contentKey, id); + var route = link.Attributes["route"] as IApiContentRoute; Assert.IsNotNull(route); Assert.AreEqual("/some-content-path", route.Path); Assert.AreEqual(postfix.NullOrWhiteSpaceAsNull(), route.QueryString); @@ -482,8 +486,10 @@ public void ParseMarkup_CanParseContentLink() var result = parser.Parse($"

"); Assert.IsTrue(result.Contains("href=\"/some-content-path\"")); + Assert.IsTrue(result.Contains($"data-destination-id=\"{_contentKey:D}\"")); Assert.IsTrue(result.Contains("data-start-item-path=\"the-root-path\"")); Assert.IsTrue(result.Contains($"data-start-item-id=\"{_contentRootKey:D}\"")); + Assert.IsTrue(result.Contains($"data-link-type=\"{LinkType.Content}\"")); } [Test] @@ -493,8 +499,10 @@ public void ParseMarkup_CanParseLegacyContentLink() var result = parser.Parse($"

"); Assert.IsTrue(result.Contains("href=\"/some-content-path\"")); + Assert.IsTrue(result.Contains($"data-destination-id=\"{_contentKey:D}\"")); Assert.IsTrue(result.Contains("data-start-item-path=\"the-root-path\"")); Assert.IsTrue(result.Contains($"data-start-item-id=\"{_contentRootKey:D}\"")); + Assert.IsTrue(result.Contains($"data-link-type=\"{LinkType.Content}\"")); } [TestCase("#some-anchor")] @@ -507,8 +515,10 @@ public void ParseMarkup_CanParseContentLink_WithPostfix(string postfix) var result = parser.Parse($"

"); Assert.IsTrue(result.Contains($"href=\"/some-content-path{postfix}\"")); + Assert.IsTrue(result.Contains($"data-destination-id=\"{_contentKey:D}\"")); Assert.IsTrue(result.Contains("data-start-item-path=\"the-root-path\"")); Assert.IsTrue(result.Contains($"data-start-item-id=\"{_contentRootKey:D}\"")); + Assert.IsTrue(result.Contains($"data-link-type=\"{LinkType.Content}\"")); } [TestCase("#some-anchor")] @@ -521,8 +531,10 @@ public void ParseMarkup_CanParseLegacyContentLink_WithPostfix(string postfix) var result = parser.Parse($"

"); Assert.IsTrue(result.Contains($"href=\"/some-content-path{postfix}\"")); + Assert.IsTrue(result.Contains($"data-destination-id=\"{_contentKey:D}\"")); Assert.IsTrue(result.Contains("data-start-item-path=\"the-root-path\"")); Assert.IsTrue(result.Contains($"data-start-item-id=\"{_contentRootKey:D}\"")); + Assert.IsTrue(result.Contains($"data-link-type=\"{LinkType.Content}\"")); } [Test] @@ -532,6 +544,8 @@ public void ParseMarkup_CanParseMediaLink() var result = parser.Parse($"

"); Assert.IsTrue(result.Contains("href=\"/some-media-url\"")); + Assert.IsTrue(result.Contains($"data-link-type=\"{LinkType.Media}\"")); + } [TestCase("{localLink:umb://document/fe5bf80d37db4373adb9b206896b4a3b}")] diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/DeliveryApi/ApiRichTextMarkupParserTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/DeliveryApi/ApiRichTextMarkupParserTests.cs index 90e8e11df262..e03a0bc9e0b8 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/DeliveryApi/ApiRichTextMarkupParserTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/DeliveryApi/ApiRichTextMarkupParserTests.cs @@ -3,6 +3,7 @@ using NUnit.Framework; using Umbraco.Cms.Core; using Umbraco.Cms.Core.DeliveryApi; +using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.DeliveryApi; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.PublishedCache; @@ -36,7 +37,7 @@ public void Can_Parse_Legacy_LocalLinks() "

link to another page

"; var expectedOutput = - "

link to another page

"; + $"

link to another page

"; var parsedHtml = parser.Parse(legacyHtml); @@ -71,8 +72,8 @@ public void Can_Parse_LocalLinks()

and to the other page

"; var expectedOutput = - @"

Rich text outside of the blocks with a link to itself

-

and to the other page

"; + $@"

Rich text outside of the blocks with a link to itself

+

and to the other page

"; var parsedHtml = parser.Parse(html); @@ -110,8 +111,8 @@ public void Can_Parse_LocalLinks_With_Postfix(string postfix)

and to the other page

"; var expectedOutput = - $@"

Rich text outside of the blocks with a link to itself

-

and to the other page

"; + $@"

Rich text outside of the blocks with a link to itself

+

and to the other page

"; var parsedHtml = parser.Parse(html);