fix!: change markdown render into ejs controller

This commit is contained in:
Innei
2022-06-26 20:09:36 +08:00
parent f83f7168e1
commit e764dd5cce
6 changed files with 149 additions and 145 deletions

View File

@@ -1,57 +1,27 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import dayjs from 'dayjs'
import { render } from 'ejs'
import JSZip from 'jszip'
import { isNil } from 'lodash'
import { join } from 'path'
import { performance } from 'perf_hooks'
import { Readable } from 'stream'
import { URL } from 'url'
import xss from 'xss'
import {
Body,
CacheTTL,
ForbiddenException,
Get,
Header,
Param,
Post,
Query,
} from '@nestjs/common'
import { Body, CacheTTL, Get, Header, Param, Post, Query } from '@nestjs/common'
import { ApiProperty } from '@nestjs/swagger'
import { ApiController } from '~/common/decorator/api-controller.decorator'
import { Auth } from '~/common/decorator/auth.decorator'
import { HttpCache } from '~/common/decorator/cache.decorator'
import { HTTPDecorators } from '~/common/decorator/http.decorator'
import { ApiName } from '~/common/decorator/openapi.decorator'
import { IsMaster } from '~/common/decorator/role.decorator'
import { ArticleTypeEnum } from '~/constants/article.constant'
import { MongoIdDto } from '~/shared/dto/id.dto'
import { getShortDateTime } from '~/utils'
import { CategoryModel } from '../category/category.model'
import { ConfigsService } from '../configs/configs.service'
import { NoteModel } from '../note/note.model'
import { PageModel } from '../page/page.model'
import { PostModel } from '../post/post.model'
import {
DataListDto,
ExportMarkdownQueryDto,
MarkdownPreviewDto,
} from './markdown.dto'
import { DataListDto, ExportMarkdownQueryDto } from './markdown.dto'
import { MarkdownYAMLProperty } from './markdown.interface'
import { MarkdownService } from './markdown.service'
@ApiController('markdown')
@ApiName
export class MarkdownController {
constructor(
private readonly service: MarkdownService,
private readonly configs: ConfigsService,
) {}
constructor(private readonly service: MarkdownService) {}
@Post('/import')
@Auth()
@@ -164,104 +134,6 @@ export class MarkdownController {
return readable
}
@Get('/render/:id')
@Header('content-type', 'text/html')
@HTTPDecorators.Bypass
@CacheTTL(60 * 60)
async renderArticle(
@Param() params: MongoIdDto,
@Query('theme') theme: string,
@IsMaster() isMaster: boolean,
) {
const { id } = params
const now = performance.now()
const [
{ html: markdownMacros, document, type },
{
url: { webUrl },
},
{ name: username },
] = await Promise.all([
this.service.renderArticle(id),
this.configs.waitForConfigReady(),
this.configs.getMaster(),
])
if (!isMaster) {
if (
('hide' in document && document.hide) ||
('password' in document && !isNil(document.password))
) {
throw new ForbiddenException('该文章已隐藏或加密')
}
}
const relativePath = (() => {
switch (type.toLowerCase()) {
case 'post':
return `/posts/${((document as PostModel).category as any).slug}/${
(document as PostModel).slug
}`
case 'note':
return `/notes/${(document as NoteModel).nid}`
case 'page':
return `/${(document as PageModel).slug}`
}
})()
const url = new URL(relativePath!, webUrl)
const structure = await this.service.getRenderedMarkdownHtmlStructure(
markdownMacros,
document.title,
theme,
)
const html = render(await this.service.getMarkdownEjsRenderTemplate(), {
...structure,
title: document.title,
footer: `<p>本文渲染于 ${getShortDateTime(
new Date(),
)},由 marked.js 解析生成,用时 ${(performance.now() - now).toFixed(
2,
)}ms</p>
<p>作者:${username},撰写于${dayjs(document.created).format('llll')}</p>
<p>原文地址:<a href="${url}">${decodeURIComponent(
url.toString(),
)}</a></p>
`,
})
return html.trim()
}
/**
* 后台预览 Markdown 可用接口, 传入 `title` 和 `md`
*/
@Post('/render')
@HttpCache.disable
@Auth()
@HTTPDecorators.Bypass
@Header('content-type', 'text/html')
async markdownPreview(
@Body() body: MarkdownPreviewDto,
@Query('theme') theme: string,
) {
const { md, title } = body
const html = this.service.renderMarkdownContent(md)
const structure = await this.service.getRenderedMarkdownHtmlStructure(
html,
title,
theme,
)
return render(await this.service.getMarkdownEjsRenderTemplate(), {
...structure,
title: xss(title),
}).trim()
}
@Get('/render/structure/:id')
@CacheTTL(60 * 60)
async getRenderedMarkdownHtmlStructure(@Param() params: MongoIdDto) {

View File

@@ -1,7 +1,137 @@
import { Controller } from '@nestjs/common'
import dayjs from 'dayjs'
import { render } from 'ejs'
import { isNil } from 'lodash'
import xss from 'xss'
import {
Body,
CacheTTL,
Controller,
ForbiddenException,
Get,
Header,
Param,
Post,
Query,
} from '@nestjs/common'
import { Auth } from '~/common/decorator/auth.decorator'
import { HttpCache } from '~/common/decorator/cache.decorator'
import { HTTPDecorators } from '~/common/decorator/http.decorator'
import { ApiName } from '~/common/decorator/openapi.decorator'
import { IsMaster } from '~/common/decorator/role.decorator'
import { MongoIdDto } from '~/shared/dto/id.dto'
import { getShortDateTime } from '~/utils'
import { ConfigsService } from '../configs/configs.service'
import { MarkdownPreviewDto } from '../markdown/markdown.dto'
import { MarkdownService } from '../markdown/markdown.service'
import { NoteModel } from '../note/note.model'
import { PageModel } from '../page/page.model'
import { PostModel } from '../post/post.model'
@ApiName
@Controller('/render')
export class RenderEjsController {}
@HTTPDecorators.Bypass
export class RenderEjsController {
constructor(
private readonly service: MarkdownService,
private readonly configs: ConfigsService,
) {}
@Get('/markdown/:id')
@Header('content-type', 'text/html')
@CacheTTL(60 * 60)
async renderArticle(
@Param() params: MongoIdDto,
@Query('theme') theme: string,
@IsMaster() isMaster: boolean,
) {
const { id } = params
const now = performance.now()
const [
{ html: markdownMacros, document, type },
{
url: { webUrl },
},
{ name: username },
] = await Promise.all([
this.service.renderArticle(id),
this.configs.waitForConfigReady(),
this.configs.getMaster(),
])
if (!isMaster) {
if (
('hide' in document && document.hide) ||
('password' in document && !isNil(document.password))
) {
throw new ForbiddenException('该文章已隐藏或加密')
}
}
const relativePath = (() => {
switch (type.toLowerCase()) {
case 'post':
return `/posts/${((document as PostModel).category as any).slug}/${
(document as PostModel).slug
}`
case 'note':
return `/notes/${(document as NoteModel).nid}`
case 'page':
return `/${(document as PageModel).slug}`
}
})()
const url = new URL(relativePath!, webUrl)
const structure = await this.service.getRenderedMarkdownHtmlStructure(
markdownMacros,
document.title,
theme,
)
const html = render(await this.service.getMarkdownEjsRenderTemplate(), {
...structure,
title: document.title,
footer: `<p>本文渲染于 ${getShortDateTime(
new Date(),
)},由 marked.js 解析生成,用时 ${(performance.now() - now).toFixed(
2,
)}ms</p>
<p>作者:${username},撰写于${dayjs(document.created).format('llll')}</p>
<p>原文地址:<a href="${url}">${decodeURIComponent(
url.toString(),
)}</a></p>
`,
})
return html.trim()
}
/**
* 后台预览 Markdown 可用接口, 传入 `title` 和 `md`
*/
@Post('/markdown')
@HttpCache.disable
@Auth()
@Header('content-type', 'text/html')
async markdownPreview(
@Body() body: MarkdownPreviewDto,
@Query('theme') theme: string,
) {
const { md, title } = body
const html = this.service.renderMarkdownContent(md)
const structure = await this.service.getRenderedMarkdownHtmlStructure(
html,
title,
theme,
)
return render(await this.service.getMarkdownEjsRenderTemplate(), {
...structure,
title: xss(title),
}).trim()
}
}

View File

@@ -1,8 +1,10 @@
import { Module } from '@nestjs/common'
import { MarkdownModule } from '../markdown/markdown.module'
import { RenderEjsController } from './render.controller'
@Module({
controllers: [RenderEjsController],
imports: [MarkdownModule],
})
export class RenderEjsModule {}