曦远 (DealiAxy)一份私人的文字刊物

StarBlog
返回文章列表折页手记阅读全文
StarBlog博客开发笔记

StarBlog Development Notes (7): Page Development — Article Detail Page

Series articles Developing a Blog Project with .NetCore: StarBlog - (1) Why write your own blog? Developing a Blog Project with .NetCore: StarBlog - (2) Environment setup and creating the project Developing a Blog Project with .NetCore: StarBlog - (3) Model design Developing a Blog Project with .NetCore: StarBlog - (4) Batch importing markdown blogs Developing a Blog Project with .NetCore: StarBlog - (5) Starting to build the Web project Developing a Blog Project with .NetCore: StarBl

曦远 (DealiAxy)约 24 分钟
目录9 个章节

Series Articles

Preface

The previous post finished developing the article list. Now it's time to write the article detail page (this update isn't late, is it? hehe).

The most important part of a blog site is arguably the article detail page. When users come to read a blog, they care first about the content, then about the reading experience, so the design of this article detail page cannot be sloppy~

Approach

The article body is stored in markdown format. To display it on a web page, the markdown needs to be rendered into HTML.

So there are two approaches:

  • One is server-side rendering: use C# to convert markdown to HTML and then render it as a web page
  • The other is to have the backend output markdown directly, and use some open-source JS libraries to render the markdown

At first I went with the first approach, server-side rendering, using the C# library Markdig. After using it more deeply, I found that some features I wanted were rather troublesome to implement. In particular, this library has almost no documentation, so customizing anything all came down to reading the source + guessing. In the end I had to give up and switch to the second approach.

This article will cover the implementation of both approaches, with the focus on the second one: client-side rendering.

Server-side Rendering

I've written a detailed introduction to the Markdig library in a previous post, so I won't repeat it here. Interested readers can check it out: Parsing Markdown documents in C# and replacing image links

First, install the Markdig library via NuGet

Markdown can be converted to HTML with a single line of code

Markdig.Markdown.ToHtml(markdownContent);

Of course, the page rendered this way is quite bare: no code highlighting, no blockquote styling, no list styles, and so on. So this alone is definitely not enough.

As the only actively maintained Markdown library for C# at present, Markdig naturally takes extensibility into account. It has an extension system, ships with more than 20 built-in extensions, and you can also install extensions developed by others to achieve effects such as code highlighting.

Using extensions is also simple: just add a pipeline parameter

var pipeline = new MarkdownPipelineBuilder().UseAdvancedExtensions().Build();
var result = Markdown.ToHtml("This is a text with some *emphasis*", pipeline);

Markdig itself does not come with a code-highlighting extension, so you need a third-party component. I tested the following two and they work

Client-side Rendering

This project ultimately went with client-side rendering. The frontend ecosystem has many markdown components; after looking around, I eventually chose the Editor.md component.

The main reasons were that it makes it relatively convenient to implement a TOC (table of contents) for the article, and it has decent highlighting.

It's simple to use

First, output the markdown into the page

<div id="test-editormd-view" class="post-content">
    <textarea id="append-test" style="display:none;">@Model.Content</textarea>
</div>

With display:none added, this textarea is not shown — there's no point showing markdown source to the user

Include the edtor.md stylesheet

<link rel="stylesheet" href="~/lib/editormd/css/editormd.preview.min.css">

Include the editor.md JS — you read that right, that's how many there are. The static assets were already installed in a previous article, so I won't repeat that here. See: (5) Starting to build the Web project

<script src="~/lib/editormd/examples/js/jquery.min.js"></script>
<script src="~/lib/editormd/lib/marked.min.js"></script>
<script src="~/lib/editormd/lib/prettify.min.js"></script>

<script src="~/lib/editormd/lib/raphael.min.js"></script>
<script src="~/lib/editormd/lib/underscore.min.js"></script>
<script src="~/lib/editormd/lib/sequence-diagram.min.js"></script>
<script src="~/lib/editormd/lib/flowchart.min.js"></script>
<script src="~/lib/editormd/lib/jquery.flowchart.min.js"></script>

<script src="~/lib/editormd/editormd.min.js"></script>

Then, use JS to call editor.md's render method

let testEditormdView = editormd.markdownToHTML("test-editormd-view", {
    // htmlDecode: "style,script,iframe",  // you can filter tags decode
    htmlDecode: true,
    //toc             : false,
    tocm: true,    // Using [TOCM]
    tocContainer: "#custom-toc-container", // 自定义 ToC 容器层
    //gfm             : false,
    //tocDropdown     : true,
    // markdownSourceCode : true, // 是否保留 Markdown 源码,即是否删除保存源码的 Textarea 标签
    emoji: true,
    taskList: true,
    tex: true,  // 默认不解析
    flowChart: true,  // 默认不解析
    sequenceDiagram: true,  // 默认不解析
})

Done.

ViewModel

The Post model is just the data stored in the database. Displaying it directly cannot fully meet the needs of the page design, so as before, we need to define a ViewModel to use.

Still placed in StarBlog.Web/ViewModels

The code is as follows

public class PostViewModel {
    public string Id { get; set; }
    public string Title { get; set; }
    public string Summary { get; set; }
    public string Content { get; set; }
    public string ContentHtml { get; set; }
    public string Path { get; set; }
    public DateTime CreationTime { get; set; }
    public DateTime LastUpdateTime { get; set; }
    public Category Category { get; set; }
    public List<Category> Categories { get; set; }
}

Compared with the Post model, it adds ContentHtml, and Categories is changed to a list

Service

That's the key rendering part. Now for some of the secondary bits~

The Service's role is to convert the Post model into a ViewModel

Let's just get to the code

public PostViewModel GetPostViewModel(Post post) {
    var vm = new PostViewModel {
        Id = post.Id,
        Title = post.Title,
        Summary = post.Summary,
        Content = post.Content,
        ContentHtml = Markdig.Markdown.ToHtml(post.Content),
        Path = post.Path,
        CreationTime = post.CreationTime,
        LastUpdateTime = post.LastUpdateTime,
        Category = post.Category,
        Categories = new List<Category>()
    };

    foreach (var itemId in post.Categories.Split(",").Select(int.Parse)) {
        var item = _categoryRepo.Where(a => a.Id == itemId).First();
        if (item != null) vm.Categories.Add(item);
    }

    return vm;
}

Although we're not using the server-side rendering approach, I still kept Markdig's server-side rendering.

View

PS: I skipped the Controller part — it's really too simple, no need to paste the code

There doesn't seem to be much to introduce here either, so I won't paste the full code. The detailed code is here: https://github.com/Deali-Axy/StarBlog/blob/master/StarBlog.Web/Views/Blog/Post.cshtml

Use Bootstrap's Grid layout for two columns: the left column shows the article's TOC, and the right column shows the article's main content.

The top of the page should show the category hierarchy, with different categories separated by "/", but there should be no slash before the first category (a complicated way of putting it)

The code that implements this requirement looks like this

<div>
    分类:
    @foreach (var category in Model.Categories) {
    @if (Model.Categories.IndexOf(category) > 0) {
    <span> / </span>
    }
    <a asp-controller="Blog" asp-action="List"
       asp-route-categoryId="@category.Id">
        @category.Name
    </a>
    }
</div>

The result looks roughly like this:

image-20220520175647471

Then we also need to improve how the time is displayed

@Model.LastUpdateTime.ToShortDateString()
@Model.LastUpdateTime.ToString("hh:mm")

The result after this is as follows

The Result

image-20220520175109987

That's about it. I may further polish the page later.

Done~

_EOF_

StarBlog Development Notes (7): Page Development — Article Detail Page

本文作者:
程序设计实验室
版权声明:
本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!

相关文章

博客上线

终于上线啦! StarBlog博客项目从春节开始,跨越三个季节终于上线了~ 其实服务器这几天已经部署好了,然后我又测试了一会,暂时觉得OK,终于可以和大家分享了~ 博客地址是:blog.deali.cn 欢迎访问哈~ PS:友链模块即将完成,欢迎各位站长大佬们来交换友链~! 系列文章 顺便回顾一下已更新的12篇文章,接下来要介绍RESTFul接口和管理后台的前端开发了,欢迎继续关注~ 基于.Net

2022-06-20 · StarBlog博客开发笔记 · 约 1 分钟

StarBlog博客开发笔记(23):文章列表接口分页、过滤、搜索、排序

前言 上一篇留的坑,火速补上。 在之前的第6篇中,已经有初步介绍,本文做一些补充,已经搞定这部分的同学可以快速跳过,基于.NetCore开发博客项目 StarBlog - (6) 页面开发之博客文章列表 对标准的WebApi来说,分页、过滤、搜索、排序是很常见的功能,既可以方便用户查看数据,又可以提升程序性能。 通用请求参数 定义一个类来作为通用的请求参数 列表接口通用的参数是这几个:PageSi

2023-01-02 · StarBlog博客开发笔记 · 约 1 分钟

讨论

共 0 条

良言一句三冬暖,恶语伤人六月寒。

加载评论中…