How to open rich text links in a new tab in Contentful.net
Using an IContentRenderer
Now don't get me wrong, I think Contentful is a great tool, but it definitely has gaps in its features. One big one for me is not being able to specify a target _blank
, etc. on a link in the rich text field type. So until this functionality is implemented I use a custom IContentRenderer
to check the start of the URL and decide how to render it.
Below is my LinkRenderer, which is pretty basic, but does the job for what I need.
using Contentful.Core.Models;
public class LinkRenderer : IContentRenderer
{
private readonly ContentRendererCollection _rendererCollection;
public LinkRenderer(ContentRendererCollection rendererCollection)
{
_rendererCollection = rendererCollection;
}
public bool SupportsContent(IContent content)
{
return content is Hyperlink;
}
public string Render(IContent content)
{
var link = (content as Hyperlink);
var sb = new StringBuilder();
if (link != null && (link.Data.Uri.StartsWith("https://") || link.Data.Uri.StartsWith("http://")))
{
sb.Append(
$"<a target=\"_blank\" rel=\"noopener\" href=\"{link?.Data?.Uri}\" title=\"{link?.Data?.Title}\">");
}
else
{
sb.Append(
$"<a href=\"{link?.Data?.Uri}\" title=\"{link?.Data?.Title}\">");
}
foreach (var subContent in link.Content)
{
var renderer = _rendererCollection.GetRendererForContent(subContent);
sb.Append(renderer.Render(subContent));
}
sb.Append("</a>");
return sb.ToString();
}
public Task<string> RenderAsync(IContent content)
{
return Task.FromResult(Render(content));
}
public int Order { get; set; }
}
N.B. There is a limitation with Contentful.net before 6.0.9. If you are using an earlier version, use the class below instead of the HtmlRenderer
, which has the ContentRendererCollection
exposed so you can use _rendererCollection.GetRendererForContent(subContent);
in the LinkRenderer
.
using Contentful.Core.Models;
public class CustomHtmlRenderer
{
private readonly ContentRendererCollection _contentRendererCollection;
//Added so I can use the ContentRendererCollection in my LinkRenderer
public ContentRendererCollection Renderers
{
get { return _contentRendererCollection; }
}
/// <summary>
/// Initializes a new instance of HtmlRenderer.
/// </summary>
public CustomHtmlRenderer() : this(new HtmlRendererOptions())
{
}
public CustomHtmlRenderer(HtmlRendererOptions options)
{
options ??= new HtmlRendererOptions();
_contentRendererCollection = new ContentRendererCollection();
_contentRendererCollection.AddRenderers(new List<IContentRenderer> {
new ParagraphRenderer(_contentRendererCollection),
new HyperlinkContentRenderer(_contentRendererCollection),
new TextRenderer(),
new HorizontalRulerContentRenderer(),
new HeadingRenderer(_contentRendererCollection),
new ListContentRenderer(_contentRendererCollection),
new ListItemContentRenderer(_contentRendererCollection, options.ListItemOptions),
new QuoteContentRenderer(_contentRendererCollection),
new AssetRenderer(_contentRendererCollection),
new NullContentRenderer()
});
}
/// <summary>
/// Renders a document to HTML.
/// </summary>
/// <param name="doc">The document to turn into HTML.</param>
/// <returns>An HTML string.</returns>
public async Task<string> ToHtml(Document doc)
{
if (!(doc?.Content?.Any() ?? false))
return await Task.Run(() => "");
var sb = new StringBuilder();
foreach (var content in doc.Content)
{
var renderer = _contentRendererCollection.GetRendererForContent(content);
sb.Append(await renderer.RenderAsync(content));
}
return sb.ToString();
}
/// <summary>
/// Adds a contentrenderer to the rendering pipeline.
/// </summary>
/// <param name="renderer"></param>
public void AddRenderer(IContentRenderer renderer)
{
_contentRendererCollection.AddRenderer(renderer);
}
}
Andy Blyth
Andy Blyth, an Optimizely MVP (OMVP) and Technical Architect at 26 DX with a keen interest in martial arts, occasionally ventures into blogging when memory serves.