/* 文件:markdown/callouts.ts — 支持 callout(提示框)语法的解析与渲染。 */ import { Tokens, MarkedExtension} from "marked"; import { Extension } from "./extension"; import AssetsManager from "src/assets"; import { wxWidget } from 'src/wechat/weixin-api'; const icon_note = `` const icon_abstract = `` const icon_info = `` const icon_todo = `` const icon_tip = `` const icon_success = `` const icon_question = `` const icon_warning = `` const icon_failure = `` const icon_danger = `` const icon_bug = `` const icon_example = `` const icon_quote = `` /* note, abstract, summary, tldr info todo tip hint, important success, check, done question, help, faq warning, caution, attention failure, fail, missing danger, error bug example quote, cite */ type CalloutInfo = {icon: string, style: string} const CalloutTypes = new Map(Object.entries({ note: { icon: icon_note, style: 'note-callout-note', }, abstract: { icon: icon_abstract, style: 'note-callout-abstract', }, summary: { icon: icon_abstract, style: 'note-callout-abstract', }, tldr: { icon: icon_abstract, style: 'note-callout-abstract', }, info: { icon: icon_info, style: 'note-callout-note', }, todo: { icon: icon_todo, style: 'note-callout-note', }, tip: { icon: icon_tip, style: 'note-callout-abstract', }, hint: { icon: icon_tip, style: 'note-callout-abstract', }, important: { icon: icon_tip, style: 'note-callout-abstract', }, success: { icon: icon_success, style: 'note-callout-success', }, check: { icon: icon_success, style: 'note-callout-success', }, done: { icon: icon_success, style: 'note-callout-success', }, question: { icon: icon_question, style: 'note-callout-question', }, help: { icon: icon_question, style: 'note-callout-question', }, faq: { icon: icon_question, style: 'note-callout-question', }, warning: { icon: icon_warning, style: 'note-callout-question', }, caution: { icon: icon_warning, style: 'note-callout-question', }, attention: { icon: icon_warning, style: 'note-callout-question', }, failure: { icon: icon_failure, style: 'note-callout-failure', }, fail: { icon: icon_failure, style: 'note-callout-failure', }, missing: { icon: icon_failure, style: 'note-callout-failure', }, danger: { icon: icon_danger, style: 'note-callout-failure', }, error: { icon: icon_danger, style: 'note-callout-failure', }, bug: { icon: icon_bug, style: 'note-callout-failure', }, example: { icon: icon_example, style: 'note-callout-example', }, quote: { icon: icon_quote, style: 'note-callout-quote', }, cite: { icon: icon_quote, style: 'note-callout-quote', } })); function GetCallout(type: string) { return CalloutTypes.get(type); }; function matchCallouts(text:string) { const regex = /\[\!(.*?)\]/g; let m; if( m = regex.exec(text)) { return m[1]; } return ""; } function GetCalloutTitle(callout:string, text:string) { let title = callout.charAt(0).toUpperCase() + callout.slice(1).toLowerCase(); let start = text.indexOf(']') + 1; if (text.indexOf(']-') > 0 || text.indexOf(']+') > 0) { start = start + 1; } let end = text.indexOf('\n'); if (end === -1) end = text.length; if (start >= end) return title; const customTitle = text.slice(start, end).trim(); if (customTitle !== '') { title = customTitle; } return title; } export class CalloutRenderer extends Extension { matched(text: string) { return matchCallouts(text) != ''; } async renderer(token: Tokens.Blockquote) { let callout = matchCallouts(token.text); if (callout == '') { const body = this.marked.parser(token.tokens); return `
${body}
`;; } const title = GetCalloutTitle(callout, token.text); const index = token.text.indexOf('\n'); let body = ''; if (index > 0) { token.text = token.text.slice(index+1) body = await this.marked.parse(token.text); } const setting = AssetsManager.getInstance().expertSettings.render?.callout as { [key: string]: any }; if (setting && callout.toLocaleLowerCase() in setting) { const authkey = this.settings.authKey; const widget = setting[callout.toLocaleLowerCase()]; if (typeof widget === 'number') { return await wxWidget(authkey, JSON.stringify({ id: `${widget}`, title, content: body, })); } if (typeof widget === 'object') { const {id, style} = widget; return await wxWidget(authkey, JSON.stringify({ id: `${id}`, title, style: style || {}, content: body, })); } } let info = GetCallout(callout.toLowerCase()); if (info == null) { const svg = await this.assetsManager.loadIcon(callout); if (svg) { info = {icon: svg, style: 'note-callout-custom'} } else { info = GetCallout('note'); } } return `
${info?.icon}${title}
${body}
`; } markedExtension(): MarkedExtension { return { async: true, walkTokens: async (token: Tokens.Generic) => { if (token.type !== 'blockquote') { return; } token.html = await this.renderer(token as Tokens.Blockquote); }, extensions:[{ name: 'blockquote', level: 'block', renderer: (token: Tokens.Generic)=> { return token.html; }, }] } } }