feat: support GFM styles to follow fonts changes & optimize custom style logic

This commit is contained in:
dribble-njr 2024-11-22 15:01:17 +08:00
parent 2c26d59d7c
commit 0375954bfc
6 changed files with 131 additions and 77 deletions

View File

@ -33,29 +33,53 @@ blockquote {
/* 引用段落样式 */ /* 引用段落样式 */
blockquote_p { blockquote_p {
} }
/* GFM 警告块 */ /* GFM note 样式 */
markdown-alert { blockquote_note {
} }
/* GFM 警告块标题 */ /* GFM tip 样式 */
markdown-alert-title { blockquote_tip {
} }
/* GFM 警告块内容,抵消 p 默认的 margin */ /* GFM important 样式 */
markdown-alert-content-wrapper { blockquote_important {
} }
/* GFM note */ /* GFM warning 样式 */
markdown-alert-title-note { blockquote_warning {
} }
/* GFM tip */ /* GFM caution 样式 */
markdown-alert-title-tip { blockquote_caution {
} }
/* GFM important */ /* GFM 通用标题 */
markdown-alert-title-important { blockquote_title {
} }
/* GFM warning */ /* GFM note 标题 */
markdown-alert-title-warning { blockquote_title_note {
} }
/* GFM caution */ /* GFM tip 标题 */
markdown-alert-title-caution { blockquote_title_tip {
}
/* GFM important 标题 */
blockquote_title_important {
}
/* GFM warning 标题 */
blockquote_title_warning {
}
/* GFM caution 标题 */
blockquote_title_caution {
}
/* GFM note 段落样式 */
blockquote_p_note {
}
/* GFM tip 段落样式 */
blockquote_p_tip {
}
/* GFM important 段落样式 */
blockquote_p_important {
}
/* GFM warning 段落样式 */
blockquote_p_warning {
}
/* GFM caution 段落样式 */
blockquote_p_caution {
} }
/* 段落样式 */ /* 段落样式 */
p { p {

View File

@ -10,7 +10,7 @@ const defaultTheme: Theme = {
}, },
block: { block: {
// 一级标题 // 一级标题
'h1': { h1: {
'display': `table`, 'display': `table`,
'padding': `0 1em`, 'padding': `0 1em`,
'border-bottom': `2px solid var(--md-primary-color)`, 'border-bottom': `2px solid var(--md-primary-color)`,
@ -22,7 +22,7 @@ const defaultTheme: Theme = {
}, },
// 二级标题 // 二级标题
'h2': { h2: {
'display': `table`, 'display': `table`,
'padding': `0 0.2em`, 'padding': `0 0.2em`,
'margin': `4em auto 2em`, 'margin': `4em auto 2em`,
@ -34,7 +34,7 @@ const defaultTheme: Theme = {
}, },
// 三级标题 // 三级标题
'h3': { h3: {
'padding-left': `8px`, 'padding-left': `8px`,
'border-left': `3px solid var(--md-primary-color)`, 'border-left': `3px solid var(--md-primary-color)`,
'margin': `2em 8px 0.75em 0`, 'margin': `2em 8px 0.75em 0`,
@ -45,7 +45,7 @@ const defaultTheme: Theme = {
}, },
// 四级标题 // 四级标题
'h4': { h4: {
'margin': `2em 8px 0.5em`, 'margin': `2em 8px 0.5em`,
'color': `var(--md-primary-color)`, 'color': `var(--md-primary-color)`,
'font-size': `1em`, 'font-size': `1em`,
@ -53,7 +53,7 @@ const defaultTheme: Theme = {
}, },
// 五级标题 // 五级标题
'h5': { h5: {
'margin': `1.5em 8px 0.5em`, 'margin': `1.5em 8px 0.5em`,
'color': `var(--md-primary-color)`, 'color': `var(--md-primary-color)`,
'font-size': `1em`, 'font-size': `1em`,
@ -61,14 +61,14 @@ const defaultTheme: Theme = {
}, },
// 六级标题 // 六级标题
'h6': { h6: {
'margin': `1.5em 8px 0.5em`, 'margin': `1.5em 8px 0.5em`,
'font-size': `1em`, 'font-size': `1em`,
'color': `var(--md-primary-color)`, 'color': `var(--md-primary-color)`,
}, },
// 段落 // 段落
'p': { p: {
'margin': `1.5em 8px`, 'margin': `1.5em 8px`,
'letter-spacing': `0.1em`, 'letter-spacing': `0.1em`,
'color': `var(--el-text-color-regular)`, 'color': `var(--el-text-color-regular)`,
@ -76,7 +76,7 @@ const defaultTheme: Theme = {
}, },
// 引用 // 引用
'blockquote': { blockquote: {
'font-style': `normal`, 'font-style': `normal`,
'border-left': `none`, 'border-left': `none`,
'padding': `1em`, 'padding': `1em`,
@ -87,57 +87,73 @@ const defaultTheme: Theme = {
}, },
// 引用内容 // 引用内容
'blockquote_p': { blockquote_p: {
'display': `block`, 'display': `block`,
'font-size': `1em`, 'font-size': `1em`,
'letter-spacing': `0.1em`, 'letter-spacing': `0.1em`,
'color': `var(--el-text-color-regular)`, 'color': `var(--el-text-color-regular)`,
}, },
// GFM 警告块 blockquote_note: {
'markdown-alert': { },
'font-style': `normal`,
'border-left': `none`, blockquote_tip: {
'padding': `1em`, },
'border-radius': `8px`,
'background': `var(--blockquote-background)`, blockquote_important: {
'margin': `2em 8px`, },
blockquote_warning: {
},
blockquote_caution: {
}, },
// GFM 警告块标题 // GFM 警告块标题
'markdown-alert-title': { blockquote_title: {
'display': `flex`, 'display': `flex`,
'align-items': `center`, 'align-items': `center`,
'gap': `0.5em`, 'gap': `0.5em`,
'margin-bottom': `0.5em`,
}, },
// GFM 警告块内容,抵消 p 默认的 margin blockquote_title_note: {
'markdown-alert-content-wrapper': {
margin: `-1em -8px -1.5em;`,
},
'markdown-alert-title-note': {
color: `#478be6`, color: `#478be6`,
}, },
'markdown-alert-title-tip': { blockquote_title_tip: {
color: `#57ab5a`, color: `#57ab5a`,
}, },
'markdown-alert-title-important': { blockquote_title_important: {
color: `#986ee2`, color: `#986ee2`,
}, },
'markdown-alert-title-warning': { blockquote_title_warning: {
color: `#c69026`, color: `#c69026`,
}, },
'markdown-alert-title-caution': { blockquote_title_caution: {
color: `#e5534b`, color: `#e5534b`,
}, },
blockquote_p_note: {
},
blockquote_p_tip: {
},
blockquote_p_important: {
},
blockquote_p_warning: {
},
blockquote_p_caution: {
},
// 代码块 // 代码块
'code_pre': { code_pre: {
'font-size': `14px`, 'font-size': `14px`,
'overflow-x': `auto`, 'overflow-x': `auto`,
'border-radius': `8px`, 'border-radius': `8px`,
@ -147,14 +163,14 @@ const defaultTheme: Theme = {
}, },
// 行内代码 // 行内代码
'code': { code: {
'margin': 0, 'margin': 0,
'white-space': `nowrap`, 'white-space': `nowrap`,
'font-family': `Menlo, Operator Mono, Consolas, Monaco, monospace`, 'font-family': `Menlo, Operator Mono, Consolas, Monaco, monospace`,
}, },
// 图片 // 图片
'image': { image: {
'display': `block`, 'display': `block`,
'width': `100% !important`, 'width': `100% !important`,
'margin': `0.1em auto 0.5em`, 'margin': `0.1em auto 0.5em`,
@ -162,32 +178,32 @@ const defaultTheme: Theme = {
}, },
// 有序列表 // 有序列表
'ol': { ol: {
'padding-left': `1em`, 'padding-left': `1em`,
'margin-left': `0`, 'margin-left': `0`,
'color': `var(--el-text-color-regular)`, 'color': `var(--el-text-color-regular)`,
}, },
// 无序列表 // 无序列表
'ul': { ul: {
'list-style': `circle`, 'list-style': `circle`,
'padding-left': `1em`, 'padding-left': `1em`,
'margin-left': `0`, 'margin-left': `0`,
'color': `var(--el-text-color-regular)`, 'color': `var(--el-text-color-regular)`,
}, },
'footnotes': { footnotes: {
'margin': `0.5em 8px`, 'margin': `0.5em 8px`,
'font-size': `80%`, 'font-size': `80%`,
'color': `var(--el-text-color-regular)`, 'color': `var(--el-text-color-regular)`,
}, },
'figure': { figure: {
margin: `1.5em 8px`, margin: `1.5em 8px`,
color: `var(--el-text-color-regular)`, color: `var(--el-text-color-regular)`,
}, },
'hr': { hr: {
'border-style': `solid`, 'border-style': `solid`,
'border-width': `1px 0 0`, 'border-width': `1px 0 0`,
'border-color': `rgba(0,0,0,0.1)`, 'border-color': `rgba(0,0,0,0.1)`,

View File

@ -2,7 +2,8 @@ import type { PropertiesHyphen } from 'csstype'
import type { Token } from 'marked' import type { Token } from 'marked'
export type Block = `h1` | `h2` | `h3` | `h4` | `h5` | `h6` | `p` | `blockquote` | `blockquote_p` | `code_pre` | `code` | `image` | `ol` | `ul` | `footnotes` | `figure` | `hr` type GFMBlock = `blockquote_note` | `blockquote_tip` | `blockquote_important` | `blockquote_warning` | `blockquote_caution` | `blockquote_title` | `blockquote_title_note` | `blockquote_title_tip` | `blockquote_title_important` | `blockquote_title_warning` | `blockquote_title_caution` | `blockquote_p` | `blockquote_p_note` | `blockquote_p_tip` | `blockquote_p_important` | `blockquote_p_warning` | `blockquote_p_caution`
export type Block = `h1` | `h2` | `h3` | `h4` | `h5` | `h6` | `p` | `blockquote` | `blockquote_p` | `code_pre` | `code` | `image` | `ol` | `ul` | `footnotes` | `figure` | `hr` | GFMBlock
export type Inline = `listitem` | `codespan` | `link` | `wx_link` | `strong` | `table` | `thead` | `td` | `footnote` | `figcaption` | `em` export type Inline = `listitem` | `codespan` | `link` | `wx_link` | `strong` | `table` | `thead` | `td` | `footnote` | `figcaption` | `em`
interface CustomCSSProperties { interface CustomCSSProperties {
@ -14,8 +15,8 @@ export type ExtendedProperties = PropertiesHyphen & CustomCSSProperties
export interface Theme { export interface Theme {
base: ExtendedProperties base: ExtendedProperties
block: Record<Block | string, ExtendedProperties> block: Record<Block, ExtendedProperties>
inline: Record<Inline | string, ExtendedProperties> inline: Record<Inline, ExtendedProperties>
} }
export interface IOpts { export interface IOpts {
@ -41,7 +42,7 @@ export interface IConfigOption<VT = string> {
export interface AlertOptions { export interface AlertOptions {
className?: string className?: string
variants?: AlertVariantItem[] variants?: AlertVariantItem[]
theme?: Theme styles?: ThemeStyles
} }
/** /**

View File

@ -29,6 +29,8 @@ export default function markedAlert(options: AlertOptions = {}): MarkedExtension
} = matchedVariant } = matchedVariant
const typeRegexp = new RegExp(createSyntaxPattern(variantType), `i`) const typeRegexp = new RegExp(createSyntaxPattern(variantType), `i`)
const { styles } = options
Object.assign(token, { Object.assign(token, {
type: `alert`, type: `alert`,
meta: { meta: {
@ -37,16 +39,17 @@ export default function markedAlert(options: AlertOptions = {}): MarkedExtension
icon, icon,
title, title,
titleClassName, titleClassName,
style: { wrapperStyle: {
...options.theme?.block[className], ...styles?.blockquote,
...options.theme?.block[`${className}-${variantType}`], ...styles?.[`blockquote_${variantType}` as keyof typeof styles],
}, },
titleStyle: { titleStyle: {
...options.theme?.block[titleClassName], ...styles?.blockquote_title,
...options.theme?.block[`${titleClassName}-${variantType}`], ...styles?.[`blockquote_title_${variantType}` as keyof typeof styles],
}, },
contentWrapperStyle: { contentStyle: {
margin: options.theme?.block[`${className}-content-wrapper`]?.margin, ...styles?.blockquote_p,
...styles?.[`blockquote_p_${variantType}` as keyof typeof styles],
}, },
}, },
}) })
@ -76,15 +79,17 @@ export default function markedAlert(options: AlertOptions = {}): MarkedExtension
name: `alert`, name: `alert`,
level: `block`, level: `block`,
renderer({ meta, tokens = [] }) { renderer({ meta, tokens = [] }) {
let tmpl = `<blockquote class="${meta.className} ${meta.className}-${meta.variant}" style='${getStyleString(meta.style)}'>\n` let text = this.parser.parse(tokens)
tmpl += `<p class="${meta.titleClassName}" style='${getStyleString(meta.titleStyle)}'>` text = text.replace(/<p .*?>/g, `<p style="${getStyleString(meta.contentStyle)}">`)
let tmpl = `<blockquote class="${meta.className} ${meta.className}-${meta.variant}" style="${getStyleString(meta.wrapperStyle)}">\n`
tmpl += `<p class="${meta.titleClassName}" style="${getStyleString(meta.titleStyle)}">`
tmpl += meta.icon.replace( tmpl += meta.icon.replace(
`<svg`, `<svg`,
`<svg style="fill: ${meta.titleStyle?.color ?? `inherit`}"`, `<svg style="fill: ${meta.titleStyle?.color ?? `inherit`}"`,
) )
tmpl += meta.title tmpl += meta.title
tmpl += `</p>\n` tmpl += `</p>\n`
tmpl += `<span style="${`${getStyleString(meta.contentWrapperStyle)} display: block;`}">${this.parser.parse(tokens)}</span>` tmpl += text
tmpl += `</blockquote>\n` tmpl += `</blockquote>\n`
return tmpl return tmpl

View File

@ -34,7 +34,7 @@ export function customizeTheme(theme: Theme, options: {
export function customCssWithTemplate(jsonString: Partial<Record<Block | Inline, PropertiesHyphen>>, color: string, theme: Theme) { export function customCssWithTemplate(jsonString: Partial<Record<Block | Inline, PropertiesHyphen>>, color: string, theme: Theme) {
const newTheme = customizeTheme(theme, { color }) const newTheme = customizeTheme(theme, { color })
const mergeProperties = <T extends Block | Inline | string = Block>(target: Record<T, PropertiesHyphen>, source: Partial<Record<Block | Inline | string, PropertiesHyphen>>, keys: T[]) => { const mergeProperties = <T extends Block | Inline = Block>(target: Record<T, PropertiesHyphen>, source: Partial<Record<Block | Inline | string, PropertiesHyphen>>, keys: T[]) => {
keys.forEach((key) => { keys.forEach((key) => {
if (source[key]) { if (source[key]) {
target[key] = Object.assign(target[key] || {}, source[key]) target[key] = Object.assign(target[key] || {}, source[key])
@ -42,7 +42,7 @@ export function customCssWithTemplate(jsonString: Partial<Record<Block | Inline,
}) })
} }
const blockKeys: (Block | string)[] = [ const blockKeys: Block[] = [
`h1`, `h1`,
`h2`, `h2`,
`h3`, `h3`,
@ -54,15 +54,23 @@ export function customCssWithTemplate(jsonString: Partial<Record<Block | Inline,
`p`, `p`,
`hr`, `hr`,
`blockquote`, `blockquote`,
`blockquote_note`,
`blockquote_tip`,
`blockquote_important`,
`blockquote_warning`,
`blockquote_caution`,
`blockquote_p`, `blockquote_p`,
`markdown-alert`, `blockquote_p_note`,
`markdown-alert-title`, `blockquote_p_tip`,
`markdown-alert-content-wrapper`, `blockquote_p_important`,
`markdown-alert-title-note`, `blockquote_p_warning`,
`markdown-alert-title-tip`, `blockquote_p_caution`,
`markdown-alert-title-important`, `blockquote_title`,
`markdown-alert-title-warning`, `blockquote_title_note`,
`markdown-alert-title-caution`, `blockquote_title_tip`,
`blockquote_title_important`,
`blockquote_title_warning`,
`blockquote_title_caution`,
`image`, `image`,
`ul`, `ul`,
`ol`, `ol`,

View File

@ -126,7 +126,7 @@ export function initRenderer(opts: IOpts) {
function setOptions(newOpts: Partial<IOpts>): void { function setOptions(newOpts: Partial<IOpts>): void {
opts = { ...opts, ...newOpts } opts = { ...opts, ...newOpts }
styleMapping = buildTheme(opts) styleMapping = buildTheme(opts)
marked.use(markedAlert({ theme: opts.theme })) marked.use(markedAlert({ styles: styleMapping }))
} }
const buildFootnotes = () => { const buildFootnotes = () => {