用Angular实现Markdown渲染的管道

哈喽!你好,很高兴见到你在奥斯汀!

简而言之

今天我想介绍一种使用Angular将Markdown渲染为HTML的方法!

在中国, 我们只需要一种选择。

以前,我介绍了如何使用Lit的Web Components来渲染Markdown的方法。

 

在Angular中,您也可以使用这个Web Component来渲染Markdown。而且,我认为这也是一个非常好的在框架中利用Web Component的例子。

然而,Angular为我们提供了强大的工具来转换数据。

这个工具叫做”Pipes”(管道)。

本篇文章介绍了如何制作自定义管道并将Markdown转换为HTML的方法。

Angular的管道是什么?

Angular的管道是Angular的HTML模板中用于转换字符串数据显示的便捷功能。

有许多现有的管道。

パイプ名機能DatePipe日付オブジェクト・数字・文字列を指定のフォーマットに変換するUpperCasePipe文字列の全てを大文字に変換するLowerCasePipe文字列の全てを小文字に変換するCurrencyPipe通貨をローカルに合わせてフォーマットするDecimalPipe指定に合わせて小数点を文字列に変換するPercentPipe指定に合わせて数字をパーセントの文字列に変換する

让我们在某个组件的 HTML 模板中尝试使用 UpperCasePipe。

content_copy
import { Component } from '@angular/core';

@Component({
  selector: 'app-hero-birthday',
  templateUrl: './my-component.component.html',
})
export class HeroBirthdayComponent {
  public name = "austin"
}
<h1>{{ this.name | uppercase }}</h1>

然后会显示如下内容:

AUSTIN

有很多这样的管道,它们是非常强大的工具。通过管道来转换显示内容是非常典型的 Angular 特色。

因此,我想要将Markdown字符串转换为HTML字符串,所以得出了使用管道的结论。

安装 marked.js

由于从零开始制作能够渲染Markdown的软件非常困难,因此我们会使用开源的优秀软件。

 

请使用以下CLI命令将其添加到Angular项目中。

npm install marked

添加自定义管道

在现有的Angular项目中,执行以下CLI命令将markdown.pipe.ts添加到项目中。

ng generate pipe pipes/markdown

※ 不需要管道,但筆者喜歡將管道放入特定的文件夾,因此這樣指定。

于是,在src/app/pipes目录下会生成两个文件:markdown.pipe.spec.ts和markdown.pipe.ts。但是,这次我们只关注markdown.pipe.ts这个文件。

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'markdown'
})
export class MarkdownPipe implements PipeTransform {

  transform(value: unknown, ...args: unknown[]): unknown {
    return null;
  }

}

这个transform类的函数能够执行一些神奇的操作。它会返回一个在HTML中渲染的结果。

使用类似于Markdown的逻辑输入,如下所示:Lit的Markdown

import { Pipe, PipeTransform } from "@angular/core";
import { marked } from "marked";

@Pipe({
  name: "markdown",
})
export class MarkdownPipe implements PipeTransform {

  transform(rawMarkdown: string) {
    return new Promise<string>((resolve, reject) => {
      marked.parse(rawMarkdown, (error, result) => {
        if (error) return reject(error);
        resolve(result);
      });
    });
  }
}

在这里出现了两个令人担忧的问题。

    1. transform函数能否返回一个Promise?

 

    如果返回HTML字符串,Angular会进行转义吗?

我们一起解决这两个问题吧。

用管道将Promise转换

首先,让我们尝试一下上述的管道。

import { Component } from "@angular/core";
import { startAppCheck } from "@firebase/app-check-module";

@Component({
  selector: "app-root",
  templateUrl: "./app.component.html",
  styleUrls: ["./app.component.css"],
})
export class AppComponent {
  public raw = `# Hello World
  
  I am a paragraph`;
}
<div>{{ this.raw | markdown }}</div>

结果 (jié guǒ)

スクリーンショット 2022-10-24 13.03.20.png

嗯,大概就是这样了吧。

只是显示了Promise.prototype.toString()的结果,而不是Promise的结果。

在这里可以帮助你的是现有的AsyncPipe。

在上述HTML模板的管道位置添加一个管道符号。

<div>{{ this.raw | markdown | async }}</div>

保存后,界面会发生变化!

スクリーンショット 2022-10-24 13.06.34.png

然而,问题还没有完全解决。

如何让生HTML在Angular中可靠地运行

由于安全原因,Angular默认会对未经处理的HTML进行正确的转义。

尽管这个功能原本很有用,但在需要显示Markdown渲染结果的HTML时却会变得妨碍。

在这种情况下,您可以将管道的结果绑定到innerHTML。

<div [innerHTML]="this.raw | markdown | async"></div>

结果 (jié guǒ)

スクリーンショット 2022-10-24 13.13.46.png

结果很理想,是吧!

使用管道将 DomSanitizer 用于中文

在markdown.pipe.ts中,您还可以注入名为DomSanitizer的库,并指定它是一个应显式避免转义的值。

import { Pipe, PipeTransform } from "@angular/core";
import { DomSanitizer } from "@angular/platform-browser";
import { marked } from "marked";

@Pipe({
  name: "markdown1",
})
export class Markdown1Pipe implements PipeTransform {
  constructor(private sanitizer: DomSanitizer) {}

  transform(rawMarkdown: string) {
    return new Promise<string>((resolve, reject) => {
      marked.parse(rawMarkdown, (error, result) => {
        if (error) return reject(error);
        resolve(result);
      });
    }).then((rawHTML) => {
      const bypassedHTML = this.sanitizer.bypassSecurityTrustHtml(rawHTML);
      return bypassedHTML;
    });
  }
}

然后,会得到与上述相同的结果,但这是非常危险的行为。

将app.component.ts中的raw值更改如下:

import { Component } from "@angular/core";
import { startAppCheck } from "@firebase/app-check-module";

@Component({
  selector: "app-root",
  templateUrl: "./app.component.html",
  styleUrls: ["./app.component.css"],
})
export class AppComponent {
  public raw = `# Hello World
  <script>alert("Hacked!");</script>
  I am a paragraph`; // XSS攻撃!アキサミヨー!

  ngAfterViewInit(): void {
    startAppCheck();
  }
}

然后, 页面会呈现以下的HTML。

スクリーンショット 2022-10-24 13.20.26.png

正如上述的DOM树所示,从Markdown中插入

广告
将在 10 秒后关闭
bannerAds