<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom"><title type="text">Blog posts by Erik Jonsson</title><link href="http://world.optimizely.com" /><updated>2023-05-12T09:51:11.0000000Z</updated><id>https://world.optimizely.com/blogs/erik-jonsson/</id> <generator uri="http://world.optimizely.com" version="2.0">Optimizely World</generator> <entry><title>CMS 12: Make it possible to create links to multi-channel content in TinyMCE.</title><link href="https://world.optimizely.com/blogs/erik-jonsson/dates/2023/5/make-it-possible-to-link-to-multi-channel-/" /><id>&lt;p&gt;In order to make the same content available in different sites with different layouts we have started to use blocks instead of pages.&lt;br /&gt;These blocks are then stored in the new multi-channel content tree in the CMS 12.&lt;br /&gt;A viewing page for each block type is used when the content shall be viewed on a specific site.&lt;/p&gt;
&lt;p&gt;Then we got the request if it could be possible to create link to these blocks in TinyMCE.&lt;br /&gt;Challenge accepted! :-)&lt;/p&gt;
&lt;p&gt;Our first thought was to make a TinyMCE plugin base on the epi-link plugin.&lt;br /&gt;After some investigation inspiration to a better solution was was found in older blog post, such as &lt;a href=&quot;https://blog.ynzen.com/extending-the-hyperlink-editor-in-optimizely-11&quot;&gt;https://blog.ynzen.com/extending-the-hyperlink-editor-in-optimizely-11&lt;/a&gt;.&lt;br /&gt;So the new task was to extend the hyperlink editor with a &quot;Multi-channel content&quot; selector.&lt;br /&gt;This was achieved by a editor descriptor.&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;[EditorDescriptorRegistration(TargetType = typeof (string), UIHint = &quot;HyperLink&quot;,
        EditorDescriptorBehavior = EditorDescriptorBehavior.OverrideDefault)]
    public class LinkEditorDescriptor : EditorDescriptor
    {
        private Injected&amp;lt;IContentLoader&amp;gt; _contentLoader;
        private readonly LocalizationService _localizationService;

        public LinkEditorDescriptor() : this(LocalizationService.Current)
        {
        }

        public LinkEditorDescriptor(LocalizationService localizationService)
        {
            _localizationService = localizationService;
        }

        public override void ModifyMetadata(ExtendedMetadata metadata, IEnumerable&amp;lt;Attribute&amp;gt; attributes)
        {
            base.ModifyMetadata(metadata, attributes);
            IEnumerable&amp;lt;IContentRepositoryDescriptor&amp;gt; allInstances =
                ServiceLocator.Current.GetAllInstances&amp;lt;IContentRepositoryDescriptor&amp;gt;();
            List&amp;lt;HyperLinkModel&amp;gt; list = (
                from r in allInstances
                orderby r.SortOrder
                where r.LinkableTypes != null &amp;amp;&amp;amp; r.LinkableTypes.Any&amp;lt;Type&amp;gt;()
                select new HyperLinkModel
                {
                    Name = (r.CustomSelectTitle ?? r.Name),
                    Roots = r.Roots,
                    WidgetType = &quot;epi-cms/widget/ContentSelector&quot;,
                    LinkableTypes = r.LinkableTypes,
                    SearchArea = r.SearchArea
                }).ToList&amp;lt;HyperLinkModel&amp;gt;();

            list.Insert(list.Count &amp;gt; 1 ? 1 : 0, 
                new HyperLinkModel
                {
                    Name = _localizationService.GetString(&quot;/episerver/cms/components/multichannel/title&quot;),
                    Title = null,
                    DisplayName = null,
                    SearchArea = &quot;CMS/blocks&quot;,
                    WidgetType = &quot;epi-cms/widget/ContentSelector&quot;,
                    Roots = GetMultiChannelRootsFromSettings(),
                    LinkableTypes = new List&amp;lt;Type&amp;gt;() { typeof(FlerKanalInnehallBlockBase) }
                }
            );
            
            metadata.EditorConfiguration[&quot;providers&quot;] = list;
            metadata.DisplayName = string.Empty;
            metadata.ClientEditingClass = &quot;epi-cms/widget/HyperLinkSelector&quot;;
        }
    }

    internal class HyperLinkModel
    {
        public string Name { get; set; }

        public string DisplayName { get; set; }

        public string Title { get; set; }

        public IEnumerable&amp;lt;ContentReference&amp;gt; Roots { get; set; }

        public string WidgetType { get; set; }

        public IDictionary&amp;lt;string, object&amp;gt; WidgetSettings { get; set; }

        public IEnumerable&amp;lt;Type&amp;gt; LinkableTypes { get; set; }

        public bool Invisible { get; set; }

        public string SearchArea { get; set; }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This makes the create link dialog look like this.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/link/5e2f92423d0341a189d5ef7a6ad6d1ef.aspx&quot; width=&quot;555&quot; alt=&quot;&quot; height=&quot;559&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The Roots parameter makes it possible to restrict the subtree(s) that shall be available in the content picker.&lt;/p&gt;
&lt;p&gt;By adding a link to a multi-channel content block an UrlFragment which points to the block is added to the XhtmlString.&lt;br /&gt;This link is rendered as a .aspx-link on the page and it generates a 404 page when clicked.&lt;br /&gt;&lt;br /&gt;In order to make the link pointing to the correct viewing page for the actual site we implemented a method to convert the link in the viewmodel.&lt;/p&gt;
&lt;pre class=&quot;language-cpp&quot;&gt;&lt;code&gt;private XhtmlString ConvertMultiSiteContentLinks(XhtmlString xhtmlString, Startpage startpage)
{
            if (string.IsNullOrWhiteSpace(xhtmlString?.ToHtmlString()) || xhtmlString.IsEmpty)
            {
                return xhtmlString;
            }

            var result = new StringBuilder();

            foreach (var fragment in xhtmlString.Fragments)
            {
                  var urlFragment = ParseFragment(fragment, startpage);
                  result.Append(urlFragment);
            }

            return new XhtmlString(result.ToString());
 }

        private string ParseFragment(IStringFragment fragment, Startpage startpage)
        {
            var urlFragment = fragment as UrlFragment;
            if (urlFragment == null)
            {
                return fragment.InternalFormat;
            }

            var contentGuid = urlFragment.ReferencedPermanentLinkIds.FirstOrDefault();

            var url = urlFragment.Url;

            if (contentGuid != Guid.Empty)
            {
                if (_contentLoader.Service.TryGet&amp;lt;MultiChannelBlockBase&amp;gt;(contentGuid, out var block))
                {
                    url = block.GenerateUrl(startpage);                
                }
            }

            return url;
        }    &lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The startpage parameter is passed to the metods to give information about on which site this link is currently rendered.&lt;/p&gt;
&lt;p&gt;All MultiChannelBlockBase blocks needs to implement the GenerateUrl(startpage) method which generates an URL to the appropriate viewing page for the block type on the actual site.&lt;br /&gt;The block ID is passed as the last segment of the URL to make it possible for the viewing page to find the correct block to render.&lt;/p&gt;
&lt;p&gt;With these parts in place we know can link to both pages and blocks from the bultin plugin in TinyMCE.&lt;/p&gt;</id><updated>2023-05-12T09:51:11.0000000Z</updated><summary type="html">Blog post</summary></entry></feed>