CMS 12: Make it possible to create links to multi-channel content in TinyMCE.
In order to make the same content available in different sites with different layouts we have started to use blocks instead of pages.
These blocks are then stored in the new multi-channel content tree in the CMS 12.
A viewing page for each block type is used when the content shall be viewed on a specific site.
Then we got the request if it could be possible to create link to these blocks in TinyMCE.
Challenge accepted! :-)
Our first thought was to make a TinyMCE plugin base on the epi-link plugin.
After some investigation inspiration to a better solution was was found in older blog post, such as https://blog.ynzen.com/extending-the-hyperlink-editor-in-optimizely-11.
So the new task was to extend the hyperlink editor with a "Multi-channel content" selector.
This was achieved by a editor descriptor.
[EditorDescriptorRegistration(TargetType = typeof (string), UIHint = "HyperLink",
EditorDescriptorBehavior = EditorDescriptorBehavior.OverrideDefault)]
public class LinkEditorDescriptor : EditorDescriptor
{
private Injected<IContentLoader> _contentLoader;
private readonly LocalizationService _localizationService;
public LinkEditorDescriptor() : this(LocalizationService.Current)
{
}
public LinkEditorDescriptor(LocalizationService localizationService)
{
_localizationService = localizationService;
}
public override void ModifyMetadata(ExtendedMetadata metadata, IEnumerable<Attribute> attributes)
{
base.ModifyMetadata(metadata, attributes);
IEnumerable<IContentRepositoryDescriptor> allInstances =
ServiceLocator.Current.GetAllInstances<IContentRepositoryDescriptor>();
List<HyperLinkModel> list = (
from r in allInstances
orderby r.SortOrder
where r.LinkableTypes != null && r.LinkableTypes.Any<Type>()
select new HyperLinkModel
{
Name = (r.CustomSelectTitle ?? r.Name),
Roots = r.Roots,
WidgetType = "epi-cms/widget/ContentSelector",
LinkableTypes = r.LinkableTypes,
SearchArea = r.SearchArea
}).ToList<HyperLinkModel>();
list.Insert(list.Count > 1 ? 1 : 0,
new HyperLinkModel
{
Name = _localizationService.GetString("/episerver/cms/components/multichannel/title"),
Title = null,
DisplayName = null,
SearchArea = "CMS/blocks",
WidgetType = "epi-cms/widget/ContentSelector",
Roots = GetMultiChannelRootsFromSettings(),
LinkableTypes = new List<Type>() { typeof(FlerKanalInnehallBlockBase) }
}
);
metadata.EditorConfiguration["providers"] = list;
metadata.DisplayName = string.Empty;
metadata.ClientEditingClass = "epi-cms/widget/HyperLinkSelector";
}
}
internal class HyperLinkModel
{
public string Name { get; set; }
public string DisplayName { get; set; }
public string Title { get; set; }
public IEnumerable<ContentReference> Roots { get; set; }
public string WidgetType { get; set; }
public IDictionary<string, object> WidgetSettings { get; set; }
public IEnumerable<Type> LinkableTypes { get; set; }
public bool Invisible { get; set; }
public string SearchArea { get; set; }
}
This makes the create link dialog look like this.
The Roots parameter makes it possible to restrict the subtree(s) that shall be available in the content picker.
By adding a link to a multi-channel content block an UrlFragment which points to the block is added to the XhtmlString.
This link is rendered as a .aspx-link on the page and it generates a 404 page when clicked.
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.
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<MultiChannelBlockBase>(contentGuid, out var block))
{
url = block.GenerateUrl(startpage);
}
}
return url;
}
The startpage parameter is passed to the metods to give information about on which site this link is currently rendered.
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.
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.
With these parts in place we know can link to both pages and blocks from the bultin plugin in TinyMCE.
Comments