feat: add styling and typography method for macors

This commit is contained in:
Innei
2022-04-30 21:51:23 +08:00
parent c2709d1e3f
commit 80bb68e0aa
4 changed files with 93 additions and 34 deletions

View File

@@ -6,8 +6,7 @@
* @FilePath: /server/apps/server/src/auth/roles.guard.ts * @FilePath: /server/apps/server/src/auth/roles.guard.ts
* Mark: Coding with Love * Mark: Coding with Love
*/ */
import { CanActivate, ExecutionContext } from '@nestjs/common' import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'
import { Injectable } from '@nestjs/common'
import { AuthGuard } from '@nestjs/passport' import { AuthGuard } from '@nestjs/passport'
import { isTest } from '~/global/env.global' import { isTest } from '~/global/env.global'

View File

@@ -178,7 +178,7 @@ export class MarkdownController {
const { id } = params const { id } = params
const now = performance.now() const now = performance.now()
const [ const [
{ html: markdown, document, type }, { html: markdownMacros, document, type },
{ {
url: { webUrl }, url: { webUrl },
}, },
@@ -212,9 +212,7 @@ export class MarkdownController {
})() })()
const url = new URL(relativePath!, webUrl) const url = new URL(relativePath!, webUrl)
const markdownMacros = this.service.renderMarkdownContent(
await this.macroService.replaceTextMacro(markdown, document),
)
const structure = await this.service.getRenderedMarkdownHtmlStructure( const structure = await this.service.getRenderedMarkdownHtmlStructure(
markdownMacros, markdownMacros,
document.title, document.title,

View File

@@ -15,6 +15,7 @@ import { ReturnModelType } from '@typegoose/typegoose'
import { DatabaseService } from '~/processors/database/database.service' import { DatabaseService } from '~/processors/database/database.service'
import { AssetService } from '~/processors/helper/helper.asset.service' import { AssetService } from '~/processors/helper/helper.asset.service'
import { TextMacroService } from '~/processors/helper/helper.macro.service'
import { InjectModel } from '~/transformers/model.transformer' import { InjectModel } from '~/transformers/model.transformer'
import { CategoryModel } from '../category/category.model' import { CategoryModel } from '../category/category.model'
@@ -39,6 +40,8 @@ export class MarkdownService {
private readonly pageModel: ReturnModelType<typeof PageModel>, private readonly pageModel: ReturnModelType<typeof PageModel>,
private readonly databaseService: DatabaseService, private readonly databaseService: DatabaseService,
private readonly macroService: TextMacroService,
) {} ) {}
async insertPostsToDb(data: DatatypeDto[]) { async insertPostsToDb(data: DatatypeDto[]) {
@@ -223,7 +226,12 @@ ${text.trim()}
} }
return { return {
html: doc.document.text, html: this.renderMarkdownContent(
await this.macroService.replaceTextMacro(
doc.document.text,
doc.document,
),
),
...doc, ...doc,
document: doc.document, document: doc.document,
} }

View File

@@ -1,24 +1,29 @@
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { FastifyRequest } from 'fastify'
import { marked } from 'marked' import { marked } from 'marked'
import { BadRequestException, Injectable, Logger } from '@nestjs/common' import {
BadRequestException,
Inject,
Injectable,
Logger,
Scope,
} from '@nestjs/common'
import { REQUEST } from '@nestjs/core'
import { UserModel } from '~/modules/user/user.model'
import { deepCloneWithFunction } from '~/utils' import { deepCloneWithFunction } from '~/utils'
import { safeEval } from '~/utils/safe-eval.util' import { safeEval } from '~/utils/safe-eval.util'
@Injectable() const logger = new Logger('TextMacroService')
export class TextMacroService { const RegMap = {
private readonly logger: Logger '#': /^#(.*?)$/g,
constructor() { $: /^\$(.*?)$/g,
this.logger = new Logger(TextMacroService.name) '?': /^\?\??(.*?)\??\?$/g,
} } as const
static readonly Reg = {
'#': /^#(.*?)$/g,
$: /^\$(.*?)$/g,
'?': /^\?\??(.*?)\??\?$/g,
}
private ifConditionGrammar<T extends object>(text: string, model: T) { class HelperStatic {
public ifConditionGrammar<T extends object>(text: string, model: T) {
const conditionSplitter = text.split('|') const conditionSplitter = text.split('|')
conditionSplitter.forEach((item: string, index: string | number) => { conditionSplitter.forEach((item: string, index: string | number) => {
conditionSplitter[index] = item.replace(/"/g, '') conditionSplitter[index] = item.replace(/"/g, '')
@@ -66,9 +71,42 @@ export class TextMacroService {
return output return output
} }
private generateFunctionContext = (variables: object) => {
return {
// time utils
dayjs: deepCloneWithFunction(dayjs),
fromNow: (time: Date | string) => dayjs(time).fromNow(),
// typography
center: (text: string) => {
return `<p align="center">${text}</p>`
},
right: (text: string) => {
return `<p align="right">${text}</p>`
},
// styling
opacity: (text: string, opacity = 0.8) => {
return `<span style="opacity: ${opacity}">${text}</span>`
},
blur: (text: string, blur = 1) => {
return `<span style="filter: blur(${blur}px)">${text}</span>`
},
color: (text: string, color = '') => {
return `<span style="color: ${color}">${text}</span>`
},
size: (text: string, size = '1em') => {
return `<span style="font-size: ${size}">${text}</span>`
},
...variables,
}
}
public async replaceTextMacro<T extends object>( public async replaceTextMacro<T extends object>(
text: string, text: string,
model: T, model: T,
extraContext: Record<string, any> = {},
): Promise<string> { ): Promise<string> {
try { try {
const matchedReg = /\[\[\s(.*?)\s\]\]/g const matchedReg = /\[\[\s(.*?)\s\]\]/g
@@ -88,19 +126,19 @@ export class TextMacroService {
} }
condition = condition?.trim() condition = condition?.trim()
if (condition.search(TextMacroService.Reg['?']) != -1) { if (condition.search(RegMap['?']) != -1) {
return this.ifConditionGrammar(condition, model) return helper.ifConditionGrammar(condition, model)
} }
if (condition.search(TextMacroService.Reg['$']) != -1) { if (condition.search(RegMap['$']) != -1) {
const variable = condition const variable = condition
.replace(TextMacroService.Reg['$'], '$1') .replace(RegMap['$'], '$1')
.replace(/\s/g, '') .replace(/\s/g, '')
return model[variable] return model[variable] ?? extraContext[variable]
} }
// eslint-disable-next-line no-useless-escape // eslint-disable-next-line no-useless-escape
if (condition.search(TextMacroService.Reg['#']) != -1) { if (condition.search(RegMap['#']) != -1) {
// eslint-disable-next-line no-useless-escape // eslint-disable-next-line no-useless-escape
const functions = condition.replace(TextMacroService.Reg['#'], '$1') const functions = condition.replace(RegMap['#'], '$1')
const variables = Object.keys(model).reduce( const variables = Object.keys(model).reduce(
(acc, key) => ({ [`$${key}`]: model[key], ...acc }), (acc, key) => ({ [`$${key}`]: model[key], ...acc }),
@@ -110,12 +148,7 @@ export class TextMacroService {
try { try {
return safeEval( return safeEval(
`return ${functions}`, `return ${functions}`,
{ this.generateFunctionContext({ ...variables, ...extraContext }),
dayjs: deepCloneWithFunction(dayjs),
fromNow: (time: Date | string) => dayjs(time).fromNow(),
...variables,
},
{ timeout: 1000 }, { timeout: 1000 },
) )
} catch { } catch {
@@ -126,8 +159,29 @@ export class TextMacroService {
} }
return text return text
} catch (err) { } catch (err) {
this.logger.log(err.message) logger.log(err.message)
return text return text
} }
} }
} }
const helper = new HelperStatic()
@Injectable({ scope: Scope.REQUEST })
export class TextMacroService {
constructor(
@Inject(REQUEST)
private readonly request: FastifyRequest & {
isMaster: boolean
user: UserModel
},
) {}
public replaceTextMacro(text: string, model: object) {
const isMaster = this.request.isMaster
return helper.replaceTextMacro(text, model, {
hideForGuest(text: string) {
return isMaster ? text : ''
},
})
}
}