返回文章列表文章索引阅读全文
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博客开发笔记(24):统一接口数据返回格式

前言 开发接口,是给客户端(Web前端、App)用的,前面说的RESTFul,是接口的规范,有了统一的接口风格,客户端开发人员在访问后端功能的时候能更快找到需要的接口,能写出可维护性更高的代码。 而接口的数据返回格式也是接口规范的重要一环,不然一个接口返回JSON,一个返回纯字符串,客户端对接到数据时一脸懵逼,没法处理啊。 合格的接口返回值应该包括状态码、提示信息和数据。 就像这样: { st

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

讨论

共 0 条

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

加载评论中…