public RSSFeedModel(NonNullImmutableList <PostWithRelatedPostStubs> posts, IRetrievePostSlugs postSlugRetriever, ICache postContentCache) { if (posts == null) { throw new ArgumentNullException(nameof(posts)); } if (!posts.Any()) { throw new ArgumentException("posts may not be an empty list"); } Posts = posts; PostSlugRetriever = postSlugRetriever ?? throw new ArgumentNullException(nameof(postSlugRetriever)); PostContentCache = postContentCache ?? throw new ArgumentNullException(nameof(postContentCache)); }
public static async Task <HtmlString> RenderPost( this IHtmlHelper helper, PostWithRelatedPostStubs post, Post previousPostIfAny, Post nextPostIfAny, IRetrievePostSlugs postSlugRetriever, ICache cache) { if (helper == null) { throw new ArgumentNullException(nameof(helper)); } if (post == null) { throw new ArgumentNullException(nameof(post)); } if (postSlugRetriever == null) { throw new ArgumentNullException(nameof(postSlugRetriever)); } if (cache == null) { throw new ArgumentNullException(nameof(cache)); } var cacheKey = string.Format( "PostHelper-RenderPost-{0}-{1}-{2}", post.Id, (previousPostIfAny == null) ? 0 : previousPostIfAny.Id, (nextPostIfAny == null) ? 0 : nextPostIfAny.Id ); var cachedData = cache[cacheKey]; if (cachedData != null) { if ((cachedData is CachablePostContent cachedPostContent) && (cachedPostContent.LastModified >= post.LastModified)) { return(new HtmlString(cachedPostContent.RenderableContent)); } cache.Remove(cacheKey); } var content = await GetRenderableContent(helper, post, previousPostIfAny, nextPostIfAny, includeTitle : true, includePostedDate : true, includeTags : true, postSlugRetriever); cache[cacheKey] = new CachablePostContent(content, post.LastModified); return(new HtmlString(content)); }
public PostListModel( string title, NonNullImmutableList <PostWithRelatedPostStubs> posts, Post previousPostIfAny, Post nextPostIfAny, NonNullImmutableList <PostStub> recent, NonNullImmutableList <PostStub> highlights, NonNullImmutableList <ArchiveMonthLink> archiveLinks, PostListDisplayOptions postListDisplay, string optionalCanonicalLinkBase, string optionalGoogleAnalyticsId, string optionalDisqusShortName, TwitterCardDetails optionalTwitterCardDetails, IRetrievePostSlugs postSlugRetriever, ICache postContentCache) { if (string.IsNullOrWhiteSpace(title)) { throw new ArgumentException("Null/blank title specified"); } if (recent == null) { throw new ArgumentNullException(nameof(recent)); } if (archiveLinks == null) { throw new ArgumentNullException(nameof(archiveLinks)); } if (!Enum.IsDefined(typeof(PostListDisplayOptions), postListDisplay)) { throw new ArgumentOutOfRangeException(nameof(postListDisplay)); } Title = title.Trim(); Posts = posts ?? throw new ArgumentNullException(nameof(posts)); PreviousPostIfAny = previousPostIfAny; NextPostIfAny = nextPostIfAny; MostRecent = recent.Sort((x, y) => - x.Posted.CompareTo(y.Posted)); Highlights = highlights ?? throw new ArgumentNullException(nameof(highlights)); ArchiveLinks = archiveLinks.Sort((x, y) => - (new DateTime(x.Year, x.Month, 1)).CompareTo(new DateTime(y.Year, y.Month, 1))); PostListDisplay = postListDisplay; OptionalCanonicalLinkBase = string.IsNullOrWhiteSpace(optionalCanonicalLinkBase) ? null : optionalCanonicalLinkBase.Trim(); OptionalGoogleAnalyticsId = string.IsNullOrWhiteSpace(optionalGoogleAnalyticsId) ? null : optionalGoogleAnalyticsId.Trim(); OptionalDisqusShortName = string.IsNullOrWhiteSpace(optionalDisqusShortName) ? null : optionalDisqusShortName.Trim(); OptionalTwitterCardDetails = optionalTwitterCardDetails; PostSlugRetriever = postSlugRetriever ?? throw new ArgumentNullException(nameof(postSlugRetriever)); PostContentCache = postContentCache ?? throw new ArgumentNullException(nameof(postContentCache)); }
public static async Task <string> RenderPostForRSS(this IHtmlHelper helper, PostWithRelatedPostStubs post, string scheme, HostString host, IRetrievePostSlugs postSlugRetriever, ICache cache) { if (helper == null) { throw new ArgumentNullException(nameof(helper)); } if (post == null) { throw new ArgumentNullException(nameof(post)); } if (string.IsNullOrWhiteSpace(scheme)) { throw new ArgumentException("Null/blank/whitespace-only scheme"); } if (!host.HasValue) { throw new ArgumentException("No host value"); } if (postSlugRetriever == null) { throw new ArgumentNullException(nameof(postSlugRetriever)); } if (cache == null) { throw new ArgumentNullException(nameof(cache)); } var cacheKey = "PostHelper-RenderPostForRSS-" + post.Id; var cachedData = cache[cacheKey]; if (cachedData != null) { if ((cachedData is CachablePostContent cachedPostContent) && (cachedPostContent.LastModified >= post.LastModified)) { return(cachedPostContent.RenderableContent); } cache.Remove(cacheKey); } var content = await GetRenderableContent(helper, post, previousPostIfAny : null, nextPostIfAny : null, includeTitle : false, includePostedDate : false, includeTags : false, postSlugRetriever); var doc = new HtmlDocument(); doc.LoadHtml(content); MakeUrlsAbsolute(doc, "a", "href", scheme, host); MakeUrlsAbsolute(doc, "img", "src", scheme, host); using (var writer = new StringWriter()) { doc.Save(writer); content = writer.ToString(); } cache[cacheKey] = new CachablePostContent(content, post.LastModified); return(content); }
/// <summary> /// Update any markdown links where the target is of the form "Post{0}" to replace with the real url /// </summary> private static async Task <string> HandlePostLinks(IHtmlHelper helper, string content, IRetrievePostSlugs postSlugRetriever) { if (helper == null) { throw new ArgumentNullException(nameof(helper)); } if (string.IsNullOrEmpty(content)) { return(content); } if (postSlugRetriever == null) { throw new ArgumentNullException(nameof(postSlugRetriever)); } var rewrittenContent = new StringBuilder(); var lastIndex = 0; foreach (Match match in Regex.Matches(content, @"\[([^\]]*?)\]\(Post(\d+)\)", RegexOptions.Multiline)) { var text = match.Groups[1].Value; var id = int.Parse(match.Groups[2].Value); rewrittenContent .Append(content, lastIndex, match.Index - lastIndex) .Append(helper.RenderedActionLink(text, "ArchiveBySlug", "ViewPost", new { Slug = await postSlugRetriever.GetSlug(id) })); lastIndex = match.Index + match.Length; } rewrittenContent.Append(content, lastIndex, content.Length - lastIndex); return(rewrittenContent.ToString()); }
private static async Task <string> GetRenderableContent( IHtmlHelper helper, PostWithRelatedPostStubs post, Post previousPostIfAny, Post nextPostIfAny, bool includeTitle, bool includePostedDate, bool includeTags, IRetrievePostSlugs postSlugRetriever) { if (helper == null) { throw new ArgumentNullException(nameof(helper)); } if (post == null) { throw new ArgumentNullException(nameof(post)); } if (postSlugRetriever == null) { throw new ArgumentNullException(nameof(postSlugRetriever)); } var markdownContent = await HandlePostLinks( helper, includeTitle?(await ReplaceFirstLineHashHeaderWithPostLink(post, postSlugRetriever)) : RemoveTitle(post.MarkdownContent), postSlugRetriever ); var content = new StringBuilder(); if (includePostedDate) { content.AppendFormat("<h3 class=\"PostDate\">{0}</h3>", post.Posted.ToString("d MMMM yyyy")); } content.Append( MarkdownTransformations.ToHtml(markdownContent) ); if (includePostedDate) { content.AppendFormat("<p class=\"PostTime\">Posted at {0}</p>", post.Posted.ToString("HH:mm")); } if ((previousPostIfAny != null) || (nextPostIfAny != null)) { content.Append("<div class=\"PreviousAndNext\">"); if (previousPostIfAny != null) { content.Append("<div class=\"Previous\">"); content.Append("<h3>Last time:</h3>"); content.Append( helper.RenderedActionLink(previousPostIfAny.Title, "ArchiveBySlug", "ViewPost", new { previousPostIfAny.Slug }, new { @class = "Previous" }) ); content.Append("</div>"); } if (nextPostIfAny != null) { content.Append("<div class=\"Next\">"); content.Append("<h3>Next:</h3>"); content.Append( helper.RenderedActionLink(nextPostIfAny.Title, "ArchiveBySlug", "ViewPost", new { nextPostIfAny.Slug }, new { @class = "Next" }) ); content.Append("</div>"); } content.Append("</div>"); } if (post.RelatedPosts.Any()) { content.Append("<div class=\"Related\">"); content.Append("<h3>You may also be interested in:</h3>"); content.Append("<ul>"); foreach (var relatedPost in post.RelatedPosts) { content.AppendFormat( "<li>{0}</li>", helper.RenderedActionLink(relatedPost.Title, "ArchiveBySlug", "ViewPost", new { relatedPost.Slug }, null) ); } content.Append("</ul>"); content.Append("</div>"); } else if (post.AutoSuggestedRelatedPosts.Any()) { // Only display the auto-suggested related posts if there are no manually-picked related posts content.Append("<div class=\"Related\">"); content.AppendFormat( "<h3>You may also be interested in (see {0} for information about how these are generated):</h3>", helper.RenderedActionLink("here", "ArchiveBySlug", "ViewPost", new { Slug = "automating-suggested-related-posts-links-for-my-blog-posts" }, null) ); content.Append("<ul>"); foreach (var relatedPost in post.AutoSuggestedRelatedPosts) { content.AppendFormat( "<li>{0}</li>", helper.RenderedActionLink(relatedPost.Title, "ArchiveBySlug", "ViewPost", new { relatedPost.Slug }, null) ); } content.Append("</ul>"); content.Append("</div>"); } if (includeTags) { content.Append(GetTagLinksContent(helper, post.Tags)); } return(content.ToString()); }