From f63504a7ccbc5fd3583bf6fb5ad2e15fe01cd9a2 Mon Sep 17 00:00:00 2001 From: dribble-njr Date: Wed, 20 Nov 2024 15:58:05 +0800 Subject: [PATCH 01/14] fix: ensure proper Markdown parsing for link text --- src/utils/renderer.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/utils/renderer.ts b/src/utils/renderer.ts index b1c18c1..d8bcacc 100644 --- a/src/utils/renderer.ts +++ b/src/utils/renderer.ts @@ -212,17 +212,19 @@ export function initRenderer(opts: IOpts) { }, link({ href, title, text }: Tokens.Link): string { + const parsedText = marked.parseInline(text) as string + if (href.startsWith(`https://mp.weixin.qq.com`)) { - return `${text}` + return `${parsedText}` } if (href === text) { - return text + return parsedText } if (opts.status) { const ref = addFootnote(title || text, href) - return `${text}[${ref}]` + return `${parsedText}[${ref}]` } - return styledContent(`link`, text, `span`) + return styledContent(`link`, parsedText, `span`) }, strong({ tokens }: Tokens.Strong): string { From e946a55f0f6a1bd51465e4600e9e3d9cf07c220b Mon Sep 17 00:00:00 2001 From: dribble-njr Date: Wed, 20 Nov 2024 16:14:53 +0800 Subject: [PATCH 02/14] chore: status to citeStatus --- src/stores/index.ts | 2 +- src/types/index.ts | 2 +- src/utils/renderer.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/stores/index.ts b/src/stores/index.ts index f96732c..087fd9f 100644 --- a/src/stores/index.ts +++ b/src/stores/index.ts @@ -143,7 +143,7 @@ export const useStore = defineStore(`store`, () => { // 更新编辑器 const editorRefresh = () => { codeThemeChange() - renderer.reset({ status: isCiteStatus.value, legend: legend.value, isUseIndent: isUseIndent.value }) + renderer.reset({ citeStatus: isCiteStatus.value, legend: legend.value, isUseIndent: isUseIndent.value }) let outputTemp = marked.parse(editor.value!.getValue()) as string // 去除第一行的 margin-top diff --git a/src/types/index.ts b/src/types/index.ts index ffc5ca2..2858a33 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -22,7 +22,7 @@ export interface IOpts { size: string isUseIndent: boolean legend?: string - status?: boolean + citeStatus?: boolean } export type ThemeStyles = Record diff --git a/src/utils/renderer.ts b/src/utils/renderer.ts index d8bcacc..39c2ad7 100644 --- a/src/utils/renderer.ts +++ b/src/utils/renderer.ts @@ -220,7 +220,7 @@ export function initRenderer(opts: IOpts) { if (href === text) { return parsedText } - if (opts.status) { + if (opts.citeStatus) { const ref = addFootnote(title || text, href) return `${parsedText}[${ref}]` } From 014e4b4aead11a0ea9b12b78478cc7d4e20a1dec Mon Sep 17 00:00:00 2001 From: dribble-njr Date: Wed, 20 Nov 2024 19:26:46 +0800 Subject: [PATCH 03/14] feat: support GFM --- README.md | 1 + src/assets/example/theme-css.txt | 21 +++++ src/config/theme.ts | 115 ++++++++++++++++------- src/stores/index.ts | 6 +- src/types/index.ts | 42 ++++++++- src/utils/MDAlert.ts | 155 +++++++++++++++++++++++++++++++ src/utils/renderer.ts | 3 + src/views/CodemirrorEditor.vue | 11 +++ 8 files changed, 317 insertions(+), 37 deletions(-) create mode 100644 src/utils/MDAlert.ts diff --git a/README.md b/README.md index d7c53b9..12a678e 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ Markdown 文档自动即时渲染为微信图文,让你不再为微信文章 - [x] 支持自定义 CSS 样式 - [x] 支持 Markdown 所有基础语法、代码块、LaTeX 公式 +- [x] 支持 [GFM 警告块](https://github.com/orgs/community/discussions/16925) - [x] 支持浅色、深色两种编辑器外观 - [x] 支持 Alt + Shift + F 快速格式化文档 - [x] 支持色盘取色,快速替换文章整体色调 diff --git a/src/assets/example/theme-css.txt b/src/assets/example/theme-css.txt index 53f9a92..230082f 100644 --- a/src/assets/example/theme-css.txt +++ b/src/assets/example/theme-css.txt @@ -33,6 +33,27 @@ blockquote { /* 引用段落样式 */ blockquote_p { } +/* GFM 警告块 */ +markdown-alert { +} +/* GFM 警告块标题 */ +markdown-alert-title { +} +/* GFM note */ +markdown-alert-title-note { +} +/* GFM tip */ +markdown-alert-title-tip { +} +/* GFM important */ +markdown-alert-title-important { +} +/* GFM warning */ +markdown-alert-title-warning { +} +/* GFM caution */ +markdown-alert-title-caution { +} /* 段落样式 */ p { } diff --git a/src/config/theme.ts b/src/config/theme.ts index 483d683..e4e3d0c 100644 --- a/src/config/theme.ts +++ b/src/config/theme.ts @@ -10,7 +10,7 @@ const defaultTheme: Theme = { }, block: { // 一级标题 - h1: { + 'h1': { 'display': `table`, 'padding': `0 1em`, 'border-bottom': `2px solid var(--md-primary-color)`, @@ -22,7 +22,7 @@ const defaultTheme: Theme = { }, // 二级标题 - h2: { + 'h2': { 'display': `table`, 'padding': `0 0.2em`, 'margin': `4em auto 2em`, @@ -34,7 +34,7 @@ const defaultTheme: Theme = { }, // 三级标题 - h3: { + 'h3': { 'padding-left': `8px`, 'border-left': `3px solid var(--md-primary-color)`, 'margin': `2em 8px 0.75em 0`, @@ -45,7 +45,7 @@ const defaultTheme: Theme = { }, // 四级标题 - h4: { + 'h4': { 'margin': `2em 8px 0.5em`, 'color': `var(--md-primary-color)`, 'font-size': `1em`, @@ -53,7 +53,7 @@ const defaultTheme: Theme = { }, // 五级标题 - h5: { + 'h5': { 'margin': `1.5em 8px 0.5em`, 'color': `var(--md-primary-color)`, 'font-size': `1em`, @@ -61,14 +61,14 @@ const defaultTheme: Theme = { }, // 六级标题 - h6: { + 'h6': { 'margin': `1.5em 8px 0.5em`, 'font-size': `1em`, 'color': `var(--md-primary-color)`, }, // 段落 - p: { + 'p': { 'margin': `1.5em 8px`, 'letter-spacing': `0.1em`, 'color': `var(--el-text-color-regular)`, @@ -76,7 +76,7 @@ const defaultTheme: Theme = { }, // 引用 - blockquote: { + 'blockquote': { 'font-style': `normal`, 'border-left': `none`, 'padding': `1em`, @@ -87,15 +87,52 @@ const defaultTheme: Theme = { }, // 引用内容 - blockquote_p: { + 'blockquote_p': { 'display': `block`, 'font-size': `1em`, 'letter-spacing': `0.1em`, 'color': `rgb(80, 80, 80)`, }, + // GFM 警告块 + 'markdown-alert': { + 'font-style': `normal`, + 'border-left': `none`, + 'padding': `1em`, + 'border-radius': `8px`, + 'background': `#f7f7f7`, + 'margin': `2em 8px`, + '--el-text-color-regular': `rgb(80, 80, 80) !important`, + }, + + // GFM 警告块标题 + 'markdown-alert-title': { + 'display': `flex`, + 'align-items': `center`, + }, + + 'markdown-alert-title-note': { + color: `#478be6`, + }, + + 'markdown-alert-title-tip': { + color: `#57ab5a`, + }, + + 'markdown-alert-title-important': { + color: `#986ee2`, + }, + + 'markdown-alert-title-warning': { + color: `#c69026`, + }, + + 'markdown-alert-title-caution': { + color: `#e5534b`, + }, + // 代码块 - code_pre: { + 'code_pre': { 'font-size': `14px`, 'overflow-x': `auto`, 'border-radius': `8px`, @@ -105,14 +142,14 @@ const defaultTheme: Theme = { }, // 行内代码 - code: { + 'code': { 'margin': 0, 'white-space': `nowrap`, 'font-family': `Menlo, Operator Mono, Consolas, Monaco, monospace`, }, // 图片 - image: { + 'image': { 'display': `block`, 'width': `100% !important`, 'margin': `0.1em auto 0.5em`, @@ -120,32 +157,32 @@ const defaultTheme: Theme = { }, // 有序列表 - ol: { + 'ol': { 'padding-left': `1em`, 'margin-left': `0`, 'color': `var(--el-text-color-regular)`, }, // 无序列表 - ul: { + 'ul': { 'list-style': `circle`, 'padding-left': `1em`, 'margin-left': `0`, 'color': `var(--el-text-color-regular)`, }, - footnotes: { + 'footnotes': { 'margin': `0.5em 8px`, 'font-size': `80%`, 'color': `var(--el-text-color-regular)`, }, - figure: { + 'figure': { margin: `1.5em 8px`, color: `var(--el-text-color-regular)`, }, - hr: { + 'hr': { 'border-style': `solid`, 'border-width': `1px 0 0`, 'border-color': `rgba(0,0,0,0.1)`, @@ -230,43 +267,43 @@ const graceTheme = toMerged(defaultTheme, { base: { }, block: { - h1: { + 'h1': { 'padding': `0.5em 1em`, 'border-bottom': `2px solid var(--md-primary-color)`, 'font-size': `1.4em`, 'text-shadow': `2px 2px 4px rgba(0,0,0,0.1)`, }, - h2: { + 'h2': { 'padding': `0.3em 1em`, 'border-radius': `8px`, 'font-size': `1.3em`, 'box-shadow': `0 4px 6px rgba(0,0,0,0.1)`, }, - h3: { + 'h3': { 'padding-left': `12px`, 'font-size': `1.2em`, 'border-left': `4px solid var(--md-primary-color)`, 'border-bottom': `1px dashed var(--md-primary-color)`, }, - h4: { + 'h4': { 'font-size': `1.1em`, }, - h5: { + 'h5': { 'font-size': `1em`, }, - h6: { + 'h6': { 'font-size': `1em`, }, - p: { + 'p': { }, - blockquote: { + 'blockquote': { 'font-style': `italic`, 'padding': `1em 1em 1em 2em`, 'border-left': `4px solid var(--md-primary-color)`, @@ -276,41 +313,51 @@ const graceTheme = toMerged(defaultTheme, { 'box-shadow': `0 4px 6px rgba(0,0,0,0.05)`, }, - blockquote_p: { + 'blockquote_p': { }, - code_pre: { + 'markdown-alert': { + 'font-style': `italic`, + 'padding': `1em 1em 1em 2em`, + 'border-left': `4px solid var(--md-primary-color)`, + 'border-radius': `6px`, + 'color': `rgba(0,0,0,0.6)`, + 'background': `linear-gradient(to right, #f7f7f7, #ffffff)`, + 'box-shadow': `0 4px 6px rgba(0,0,0,0.05)`, + }, + + 'code_pre': { 'box-shadow': `inset 0 0 10px rgba(0,0,0,0.05)`, }, - code: { + 'code': { 'white-space': `pre-wrap`, 'font-family': `'Fira Code', Menlo, Operator Mono, Consolas, Monaco, monospace`, }, - image: { + 'image': { 'border-radius': `8px`, 'box-shadow': `0 4px 8px rgba(0,0,0,0.1)`, }, - ol: { + 'ol': { 'padding-left': `1.5em`, }, - ul: { + 'ul': { 'list-style': `none`, 'padding-left': `1.5em`, }, - footnotes: { + 'footnotes': { }, - figure: { + 'figure': { }, - hr: { + 'hr': { height: `1px`, border: `none`, margin: `2em 0`, diff --git a/src/stores/index.ts b/src/stores/index.ts index 087fd9f..43ab9ed 100644 --- a/src/stores/index.ts +++ b/src/stores/index.ts @@ -2,6 +2,7 @@ import DEFAULT_CONTENT from '@/assets/example/markdown.md?raw' import DEFAULT_CSS_CONTENT from '@/assets/example/theme-css.txt?raw' import { altKey, codeBlockThemeOptions, colorOptions, fontFamilyOptions, fontSizeOptions, legendOptions, shiftKey, themeMap, themeOptions } from '@/config' import { addPrefix, css2json, customCssWithTemplate, customizeTheme, downloadMD, exportHTML, formatDoc } from '@/utils' +import markedAlert from '@/utils/MDAlert' import { initRenderer } from '@/utils/renderer' import { useDark, useStorage, useToggle } from '@vueuse/core' @@ -144,6 +145,7 @@ export const useStore = defineStore(`store`, () => { const editorRefresh = () => { codeThemeChange() renderer.reset({ citeStatus: isCiteStatus.value, legend: legend.value, isUseIndent: isUseIndent.value }) + let outputTemp = marked.parse(editor.value!.getValue()) as string // 去除第一行的 margin-top @@ -184,6 +186,8 @@ export const useStore = defineStore(`store`, () => { renderer.setOptions({ theme: newTheme, }) + marked.use(markedAlert({ theme: newTheme })) + editorRefresh() } // 初始化 CSS 编辑器 @@ -354,7 +358,7 @@ export const useStore = defineStore(`store`, () => { const reader = new FileReader() reader.readAsText(file) reader.onload = (event) => { - (editor.value!).setValue((event.target !).result as string) + (editor.value!).setValue((event.target!).result as string) ElMessage.success(`文档导入成功`) } } diff --git a/src/types/index.ts b/src/types/index.ts index 2858a33..28742e4 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,5 +1,7 @@ import type { PropertiesHyphen } from 'csstype' +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` export type Inline = `listitem` | `codespan` | `link` | `wx_link` | `strong` | `table` | `thead` | `td` | `footnote` | `figcaption` | `em` @@ -12,8 +14,8 @@ export type ExtendedProperties = PropertiesHyphen & CustomCSSProperties export interface Theme { base: ExtendedProperties - block: Record - inline: Record + block: Record + inline: Record } export interface IOpts { @@ -32,3 +34,39 @@ export interface IConfigOption { value: VT desc: string } + +/** + * Options for the `markedAlert` extension. + */ +export interface AlertOptions { + className?: string + variants?: AlertVariantItem[] + theme?: Theme +} + +/** + * Configuration for an alert type. + */ +export interface AlertVariantItem { + type: string + icon: string + title?: string + titleClassName?: string +} + +/** + * Represents an alert token. + */ +export interface Alert { + type: `alert` + meta: { + className: string + variant: string + icon: string + title: string + titleClassName: string + } + raw: string + text: string + tokens: Token[] +} diff --git a/src/utils/MDAlert.ts b/src/utils/MDAlert.ts new file mode 100644 index 0000000..6b0bca1 --- /dev/null +++ b/src/utils/MDAlert.ts @@ -0,0 +1,155 @@ +import type { AlertOptions, AlertVariantItem } from '@/types' +import type { MarkedExtension, Tokens } from 'marked' + +/** + * https://github.com/bent10/marked-extensions/tree/main/packages/alert + * To support theme, we need to modify the source code. + * A [marked](https://marked.js.org/) extension to support [GFM alerts](https://github.com/orgs/community/discussions/16925). + */ +export default function markedAlert(options: AlertOptions = {}): MarkedExtension { + const { className = `markdown-alert`, variants = [] } = options + const resolvedVariants = resolveVariants(variants) + + return { + walkTokens(token) { + if (token.type !== `blockquote`) + return + + const matchedVariant = resolvedVariants.find(({ type }) => + new RegExp(createSyntaxPattern(type)).test(token.text), + ) + + if (matchedVariant) { + const { + type: variantType, + icon, + title = ucfirst(variantType), + titleClassName = `${className}-title`, + } = matchedVariant + const typeRegexp = new RegExp(createSyntaxPattern(variantType)) + + Object.assign(token, { + type: `alert`, + meta: { + className, + variant: variantType, + icon, + title, + titleClassName, + style: { + ...options.theme?.block[className], + ...options.theme?.block[`${className}-${variantType}`], + }, + titleStyle: { + ...options.theme?.block[titleClassName], + ...options.theme?.block[`${titleClassName}-${variantType}`], + }, + }, + }) + + console.log({ + ...options.theme?.block[className], + ...options.theme?.block[`${className}-${variantType}`], + }, `style`) + + const firstLine = token.tokens?.[0] as Tokens.Paragraph + const firstLineText = firstLine.raw?.replace(typeRegexp, ``).trim() + + if (firstLineText) { + const patternToken = firstLine.tokens[0] as Tokens.Text + + Object.assign(patternToken, { + raw: patternToken.raw.replace(typeRegexp, ``), + text: patternToken.text.replace(typeRegexp, ``), + }) + + if (firstLine.tokens[1]?.type === `br`) { + firstLine.tokens.splice(1, 1) + } + } + else { + token.tokens?.shift() + } + } + }, + extensions: [ + { + name: `alert`, + level: `block`, + renderer({ meta, tokens = [] }) { + let tmpl = `
\n` + tmpl += `

` + tmpl += meta.icon.replace( + `\n` + tmpl += this.parser.parse(tokens) + tmpl += `

\n` + + return tmpl + }, + }, + ], + } +} + +/** + * The default configuration for alert variants. + */ +const defaultAlertVariant: AlertVariantItem[] = [ + { + type: `note`, + icon: ``, + }, + { + type: `tip`, + icon: ``, + }, + { + type: `important`, + icon: ``, + }, + { + type: `warning`, + icon: ``, + }, + { + type: `caution`, + icon: ``, + }, +] + +/** + * Resolves the variants configuration, combining the provided variants with + * the default variants. + */ +export function resolveVariants(variants: AlertVariantItem[]) { + if (!variants.length) + return defaultAlertVariant + + return Object.values( + [...defaultAlertVariant, ...variants].reduce( + (map, item) => { + map[item.type] = item + return map + }, + {} as { [key: string]: AlertVariantItem }, + ), + ) +} + +/** + * Returns regex pattern to match alert syntax. + */ +export function createSyntaxPattern(type: string) { + return `^(?:\\[!${type.toUpperCase()}])\\s*?\n*` +} + +/** + * Capitalizes the first letter of a string. + */ +export function ucfirst(str: string) { + return str.slice(0, 1).toUpperCase() + str.slice(1).toLowerCase() +} diff --git a/src/utils/renderer.ts b/src/utils/renderer.ts index 39c2ad7..17d12dd 100644 --- a/src/utils/renderer.ts +++ b/src/utils/renderer.ts @@ -6,6 +6,7 @@ import hljs from 'highlight.js' import { marked } from 'marked' import mermaid from 'mermaid' +import markedAlert from './MDAlert' import { MDKatex } from './MDKatex' marked.use(MDKatex({ nonStandard: true })) @@ -103,6 +104,8 @@ export function initRenderer(opts: IOpts) { let listIndex: number = 0 let isOrdered: boolean = false + marked.use(markedAlert({ theme: opts.theme })) + function styles(tag: string, addition: string = ``): string { return getStyles(styleMapping, tag, addition) } diff --git a/src/views/CodemirrorEditor.vue b/src/views/CodemirrorEditor.vue index d1c1b64..cb338bf 100644 --- a/src/views/CodemirrorEditor.vue +++ b/src/views/CodemirrorEditor.vue @@ -519,6 +519,17 @@ onMounted(() => { border-spacing: 0; } +:deep(.markdown-alert) { + display: block; + padding: 1em; + border-radius: 8px; + background: #f7f7f7; +} + +:deep(.markdown-alert-title) { + font-weight: bold; +} + .codeMirror-wrapper, .preview-wrapper { height: 100%; From 8dcc2c27562fb1986c777b83e013f5f131e65931 Mon Sep 17 00:00:00 2001 From: dribble-njr Date: Wed, 20 Nov 2024 21:38:08 +0800 Subject: [PATCH 04/14] feat: GFM style changed by theme --- src/stores/index.ts | 2 -- src/utils/renderer.ts | 3 +-- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/stores/index.ts b/src/stores/index.ts index 43ab9ed..e91ff65 100644 --- a/src/stores/index.ts +++ b/src/stores/index.ts @@ -2,7 +2,6 @@ import DEFAULT_CONTENT from '@/assets/example/markdown.md?raw' import DEFAULT_CSS_CONTENT from '@/assets/example/theme-css.txt?raw' import { altKey, codeBlockThemeOptions, colorOptions, fontFamilyOptions, fontSizeOptions, legendOptions, shiftKey, themeMap, themeOptions } from '@/config' import { addPrefix, css2json, customCssWithTemplate, customizeTheme, downloadMD, exportHTML, formatDoc } from '@/utils' -import markedAlert from '@/utils/MDAlert' import { initRenderer } from '@/utils/renderer' import { useDark, useStorage, useToggle } from '@vueuse/core' @@ -186,7 +185,6 @@ export const useStore = defineStore(`store`, () => { renderer.setOptions({ theme: newTheme, }) - marked.use(markedAlert({ theme: newTheme })) editorRefresh() } diff --git a/src/utils/renderer.ts b/src/utils/renderer.ts index 17d12dd..ad9d991 100644 --- a/src/utils/renderer.ts +++ b/src/utils/renderer.ts @@ -104,8 +104,6 @@ export function initRenderer(opts: IOpts) { let listIndex: number = 0 let isOrdered: boolean = false - marked.use(markedAlert({ theme: opts.theme })) - function styles(tag: string, addition: string = ``): string { return getStyles(styleMapping, tag, addition) } @@ -129,6 +127,7 @@ export function initRenderer(opts: IOpts) { function setOptions(newOpts: Partial): void { opts = { ...opts, ...newOpts } styleMapping = buildTheme(opts) + marked.use(markedAlert({ theme: opts.theme })) } const buildFootnotes = () => { From 01e255d826263f2da651d8cbffdc8b36f816de30 Mon Sep 17 00:00:00 2001 From: dribble-njr Date: Wed, 20 Nov 2024 22:13:35 +0800 Subject: [PATCH 05/14] feat: offsetting the default margin of p --- src/assets/example/theme-css.txt | 3 +++ src/config/theme.ts | 5 +++++ src/utils/MDAlert.ts | 10 +++++++--- src/utils/index.ts | 11 ++++++++++- src/utils/renderer.ts | 5 ++--- 5 files changed, 27 insertions(+), 7 deletions(-) diff --git a/src/assets/example/theme-css.txt b/src/assets/example/theme-css.txt index 230082f..9c04218 100644 --- a/src/assets/example/theme-css.txt +++ b/src/assets/example/theme-css.txt @@ -39,6 +39,9 @@ markdown-alert { /* GFM 警告块标题 */ markdown-alert-title { } +/* GFM 警告块内容,抵消 p 默认的 margin */ +markdown-alert-content-wrapper { +} /* GFM note */ markdown-alert-title-note { } diff --git a/src/config/theme.ts b/src/config/theme.ts index e4e3d0c..5de9a61 100644 --- a/src/config/theme.ts +++ b/src/config/theme.ts @@ -111,6 +111,11 @@ const defaultTheme: Theme = { 'align-items': `center`, }, + // GFM 警告块内容,抵消 p 默认的 margin + 'markdown-alert-content-wrapper': { + margin: `-1em -8px -1.5em;`, + }, + 'markdown-alert-title-note': { color: `#478be6`, }, diff --git a/src/utils/MDAlert.ts b/src/utils/MDAlert.ts index 6b0bca1..9028d2e 100644 --- a/src/utils/MDAlert.ts +++ b/src/utils/MDAlert.ts @@ -1,5 +1,6 @@ import type { AlertOptions, AlertVariantItem } from '@/types' import type { MarkedExtension, Tokens } from 'marked' +import { getStyleString } from '.' /** * https://github.com/bent10/marked-extensions/tree/main/packages/alert @@ -44,6 +45,9 @@ export default function markedAlert(options: AlertOptions = {}): MarkedExtension ...options.theme?.block[titleClassName], ...options.theme?.block[`${titleClassName}-${variantType}`], }, + contentWrapperStyle: { + margin: options.theme?.block[`${className}-content-wrapper`]?.margin, + }, }, }) @@ -77,15 +81,15 @@ export default function markedAlert(options: AlertOptions = {}): MarkedExtension name: `alert`, level: `block`, renderer({ meta, tokens = [] }) { - let tmpl = `
\n` - tmpl += `

` + let tmpl = `

\n` + tmpl += `

` tmpl += meta.icon.replace( `\n` - tmpl += this.parser.parse(tokens) + tmpl += `

${this.parser.parse(tokens)}
` tmpl += `
\n` return tmpl diff --git a/src/utils/index.ts b/src/utils/index.ts index c787cfa..a18c86c 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,4 +1,4 @@ -import type { Block, Inline, Theme } from '@/types' +import type { Block, ExtendedProperties, Inline, Theme } from '@/types' import type { PropertiesHyphen } from 'csstype' import { prefix } from '@/config' @@ -116,6 +116,15 @@ export function css2json(css: string): Partial `${key}: ${value}`).join(`; `) +} + /** * 格式化内容 * @param {string} content - 要格式化的内容 diff --git a/src/utils/renderer.ts b/src/utils/renderer.ts index ad9d991..ca43515 100644 --- a/src/utils/renderer.ts +++ b/src/utils/renderer.ts @@ -6,6 +6,7 @@ import hljs from 'highlight.js' import { marked } from 'marked' import mermaid from 'mermaid' +import { getStyleString } from '.' import markedAlert from './MDAlert' import { MDKatex } from './MDKatex' @@ -59,9 +60,7 @@ function getStyles(styleMapping: ThemeStyles, tokenName: string, addition: strin if (!dict) { return `` } - const styles = Object.entries(dict) - .map(([key, value]) => `${key}:${value}`) - .join(`;`) + const styles = getStyleString(dict) return `style="${styles}${addition}"` } From a94da25b96dbea409ab35f90b66edcc4a5129a82 Mon Sep 17 00:00:00 2001 From: dribble-njr Date: Wed, 20 Nov 2024 23:15:31 +0800 Subject: [PATCH 06/14] fix: copy to wx lost style --- src/config/theme.ts | 1 + src/utils/MDAlert.ts | 14 +++++++------- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/config/theme.ts b/src/config/theme.ts index 5de9a61..f21cec1 100644 --- a/src/config/theme.ts +++ b/src/config/theme.ts @@ -109,6 +109,7 @@ const defaultTheme: Theme = { 'markdown-alert-title': { 'display': `flex`, 'align-items': `center`, + 'gap': `0.5em`, }, // GFM 警告块内容,抵消 p 默认的 margin diff --git a/src/utils/MDAlert.ts b/src/utils/MDAlert.ts index 9028d2e..e53be83 100644 --- a/src/utils/MDAlert.ts +++ b/src/utils/MDAlert.ts @@ -81,7 +81,7 @@ export default function markedAlert(options: AlertOptions = {}): MarkedExtension name: `alert`, level: `block`, renderer({ meta, tokens = [] }) { - let tmpl = `
\n` + let tmpl = `
\n` tmpl += `

` tmpl += meta.icon.replace( `\n` - tmpl += `

${this.parser.parse(tokens)}
` - tmpl += `
\n` + tmpl += `${this.parser.parse(tokens)}` + tmpl += `\n` return tmpl }, @@ -105,19 +105,19 @@ export default function markedAlert(options: AlertOptions = {}): MarkedExtension const defaultAlertVariant: AlertVariantItem[] = [ { type: `note`, - icon: ``, + icon: ``, }, { type: `tip`, - icon: ``, + icon: ``, }, { type: `important`, - icon: ``, + icon: ``, }, { type: `warning`, - icon: ``, + icon: ``, }, { type: `caution`, From a25dedf81146d931fbb107e6571ea3f349c99663 Mon Sep 17 00:00:00 2001 From: dribble-njr Date: Thu, 21 Nov 2024 11:32:37 +0800 Subject: [PATCH 07/14] fix: ensure SVG elements are included in copy --- src/components/CodemirrorEditor/EditorHeader/index.vue | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/components/CodemirrorEditor/EditorHeader/index.vue b/src/components/CodemirrorEditor/EditorHeader/index.vue index 28d106f..dc88269 100644 --- a/src/components/CodemirrorEditor/EditorHeader/index.vue +++ b/src/components/CodemirrorEditor/EditorHeader/index.vue @@ -125,7 +125,17 @@ function copy() { .replaceAll(`var(--el-text-color-regular)`, `#3f3f3f`) .replaceAll(`var(--md-primary-color)`, primaryColor.value) .replaceAll(/--md-primary-color:.+?;/g, ``) + clipboardDiv.focus() + + // edge case: 由于 svg 无法复制, 在前面插入一个空节点 + const p = document.createElement(`p`) + p.style.fontSize = `0` // 设置字体大小为 0 + p.style.lineHeight = `0` // 行高也为 0 + p.style.margin = `0` // 避免外边距干扰 + p.innerHTML = ` ` + clipboardDiv.insertBefore(p, clipboardDiv.firstChild) + window.getSelection()!.removeAllRanges() const range = document.createRange() From 4c16dcf34f54fcea3fc7caecb345a76b3fa59cb3 Mon Sep 17 00:00:00 2001 From: dribble-njr Date: Thu, 21 Nov 2024 13:10:58 +0800 Subject: [PATCH 08/14] fix: code block render abnormal --- src/stores/index.ts | 28 ++++++++++++++++------------ src/utils/renderer.ts | 4 ++-- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/stores/index.ts b/src/stores/index.ts index e91ff65..7a0921e 100644 --- a/src/stores/index.ts +++ b/src/stores/index.ts @@ -158,23 +158,27 @@ export const useStore = defineStore(`store`, () => { outputTemp += ` ` } + outputTemp += ` + + ` + output.value = outputTemp } diff --git a/src/utils/renderer.ts b/src/utils/renderer.ts index ca43515..3b9df97 100644 --- a/src/utils/renderer.ts +++ b/src/utils/renderer.ts @@ -89,9 +89,9 @@ function transform(legend: string, text: string | null, title: string | null): s const macCodeSvg = ` - + - + `.trim() From 8927c0a4c7fd585208fcda9aecffd2ecd067028d Mon Sep 17 00:00:00 2001 From: dribble-njr Date: Thu, 21 Nov 2024 13:21:39 +0800 Subject: [PATCH 09/14] chore: remove useless style --- src/views/CodemirrorEditor.vue | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/views/CodemirrorEditor.vue b/src/views/CodemirrorEditor.vue index cb338bf..d1c1b64 100644 --- a/src/views/CodemirrorEditor.vue +++ b/src/views/CodemirrorEditor.vue @@ -519,17 +519,6 @@ onMounted(() => { border-spacing: 0; } -:deep(.markdown-alert) { - display: block; - padding: 1em; - border-radius: 8px; - background: #f7f7f7; -} - -:deep(.markdown-alert-title) { - font-weight: bold; -} - .codeMirror-wrapper, .preview-wrapper { height: 100%; From 617ad219c430bba710912cb27dd9b6ef82f998bd Mon Sep 17 00:00:00 2001 From: dribble-njr Date: Thu, 21 Nov 2024 14:37:46 +0800 Subject: [PATCH 10/14] fix: maximum call stack size exceeded in parse link --- src/utils/renderer.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/utils/renderer.ts b/src/utils/renderer.ts index 3b9df97..5f6729a 100644 --- a/src/utils/renderer.ts +++ b/src/utils/renderer.ts @@ -212,9 +212,8 @@ export function initRenderer(opts: IOpts) { return `
${text}${subText}
` }, - link({ href, title, text }: Tokens.Link): string { - const parsedText = marked.parseInline(text) as string - + link({ href, title, text, tokens }: Tokens.Link): string { + const parsedText = this.parser.parseInline(tokens) if (href.startsWith(`https://mp.weixin.qq.com`)) { return `${parsedText}` } From c48f6419d357c77e2590e018ed5ff930a1d9a16e Mon Sep 17 00:00:00 2001 From: dribble-njr Date: Thu, 21 Nov 2024 22:56:56 +0800 Subject: [PATCH 11/14] feat: support dark mode in blockquote preview --- src/assets/index.css | 4 ++++ src/components/CodemirrorEditor/EditorHeader/index.vue | 1 + src/config/theme.ts | 7 +++---- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/assets/index.css b/src/assets/index.css index c041c3c..cd105a8 100644 --- a/src/assets/index.css +++ b/src/assets/index.css @@ -32,6 +32,8 @@ --input:0 0% 89.8%; --ring:0 0% 3.9%; --radius: 0.5rem; + + --blockquote-background: #f7f7f7; } .dark { @@ -62,6 +64,8 @@ --border:0 0% 14.9%; --input:0 0% 14.9%; --ring:0 0% 83.1%; + + --blockquote-background: #212121; } } diff --git a/src/components/CodemirrorEditor/EditorHeader/index.vue b/src/components/CodemirrorEditor/EditorHeader/index.vue index dc88269..3f65709 100644 --- a/src/components/CodemirrorEditor/EditorHeader/index.vue +++ b/src/components/CodemirrorEditor/EditorHeader/index.vue @@ -123,6 +123,7 @@ function copy() { .replace(/top:(.*?)em/g, `transform: translateY($1em)`) // 适配主题中的颜色变量 .replaceAll(`var(--el-text-color-regular)`, `#3f3f3f`) + .replaceAll(`var(--blockquote-background)`, `#f7f7f7`) .replaceAll(`var(--md-primary-color)`, primaryColor.value) .replaceAll(/--md-primary-color:.+?;/g, ``) diff --git a/src/config/theme.ts b/src/config/theme.ts index f21cec1..2453577 100644 --- a/src/config/theme.ts +++ b/src/config/theme.ts @@ -82,7 +82,7 @@ const defaultTheme: Theme = { 'padding': `1em`, 'border-radius': `8px`, 'color': `rgba(0,0,0,0.5)`, - 'background': `#f7f7f7`, + 'background': `var(--blockquote-background)`, 'margin': `2em 8px`, }, @@ -91,7 +91,7 @@ const defaultTheme: Theme = { 'display': `block`, 'font-size': `1em`, 'letter-spacing': `0.1em`, - 'color': `rgb(80, 80, 80)`, + 'color': `var(--el-text-color-regular)`, }, // GFM 警告块 @@ -100,9 +100,8 @@ const defaultTheme: Theme = { 'border-left': `none`, 'padding': `1em`, 'border-radius': `8px`, - 'background': `#f7f7f7`, + 'background': `var(--blockquote-background)`, 'margin': `2em 8px`, - '--el-text-color-regular': `rgb(80, 80, 80) !important`, }, // GFM 警告块标题 From b9953ab2e7deab025d61c4c8c913047bc31cbb01 Mon Sep 17 00:00:00 2001 From: dribble-njr Date: Thu, 21 Nov 2024 23:01:40 +0800 Subject: [PATCH 12/14] feat: add case-insensitivity to alert type matching --- src/utils/MDAlert.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/MDAlert.ts b/src/utils/MDAlert.ts index e53be83..a2942ee 100644 --- a/src/utils/MDAlert.ts +++ b/src/utils/MDAlert.ts @@ -17,7 +17,7 @@ export default function markedAlert(options: AlertOptions = {}): MarkedExtension return const matchedVariant = resolvedVariants.find(({ type }) => - new RegExp(createSyntaxPattern(type)).test(token.text), + new RegExp(createSyntaxPattern(type), `i`).test(token.text), ) if (matchedVariant) { @@ -148,7 +148,7 @@ export function resolveVariants(variants: AlertVariantItem[]) { * Returns regex pattern to match alert syntax. */ export function createSyntaxPattern(type: string) { - return `^(?:\\[!${type.toUpperCase()}])\\s*?\n*` + return `^(?:\\[!${type}])\\s*?\n*` } /** From f55285d93b8d32990ac1bc54f73b183145cd7158 Mon Sep 17 00:00:00 2001 From: dribble-njr Date: Fri, 22 Nov 2024 09:43:12 +0800 Subject: [PATCH 13/14] fix: duplicate render in GFM alerts --- src/utils/MDAlert.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/MDAlert.ts b/src/utils/MDAlert.ts index a2942ee..58118c0 100644 --- a/src/utils/MDAlert.ts +++ b/src/utils/MDAlert.ts @@ -27,7 +27,7 @@ export default function markedAlert(options: AlertOptions = {}): MarkedExtension title = ucfirst(variantType), titleClassName = `${className}-title`, } = matchedVariant - const typeRegexp = new RegExp(createSyntaxPattern(variantType)) + const typeRegexp = new RegExp(createSyntaxPattern(variantType), `i`) Object.assign(token, { type: `alert`, From 5e2535f143dd31edf488379d2046b02205284185 Mon Sep 17 00:00:00 2001 From: dribble-njr Date: Fri, 22 Nov 2024 09:55:16 +0800 Subject: [PATCH 14/14] feat: support grace theme blockquote preview in dark mode --- src/config/theme.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/config/theme.ts b/src/config/theme.ts index 2453577..842fafe 100644 --- a/src/config/theme.ts +++ b/src/config/theme.ts @@ -314,7 +314,6 @@ const graceTheme = toMerged(defaultTheme, { 'border-left': `4px solid var(--md-primary-color)`, 'border-radius': `6px`, 'color': `rgba(0,0,0,0.6)`, - 'background': `linear-gradient(to right, #f7f7f7, #ffffff)`, 'box-shadow': `0 4px 6px rgba(0,0,0,0.05)`, }, @@ -323,12 +322,6 @@ const graceTheme = toMerged(defaultTheme, { 'markdown-alert': { 'font-style': `italic`, - 'padding': `1em 1em 1em 2em`, - 'border-left': `4px solid var(--md-primary-color)`, - 'border-radius': `6px`, - 'color': `rgba(0,0,0,0.6)`, - 'background': `linear-gradient(to right, #f7f7f7, #ffffff)`, - 'box-shadow': `0 4px 6px rgba(0,0,0,0.05)`, }, 'code_pre': {