import katex from 'katex' import prism, { loadedCache, transfromAliasToOrigin } from '../../../prism/' import { CLASS_OR_ID, DEVICE_MEMORY, PREVIEW_DOMPURIFY_CONFIG, HAS_TEXT_BLOCK_REG } from '../../../config' import { tokenizer } from '../../' import { snakeToCamel, sanitize, escapeHtml, getLongUniqueId, getImageInfo } from '../../../utils' import { h, htmlToVNode } from '../snabbdom' // todo@jocs any better solutions? const MARKER_HASK = { '<': `%${getLongUniqueId()}%`, '>': `%${getLongUniqueId()}%`, '"': `%${getLongUniqueId()}%`, "'": `%${getLongUniqueId()}%` } const getHighlightHtml = (text, highlights, escape = false, handleLineEnding = false) => { let code = '' let pos = 0 const getEscapeHTML = (className, content) => { return `${MARKER_HASK['<']}span class=${MARKER_HASK['"']}${className}${MARKER_HASK['"']}${MARKER_HASK['>']}${content}${MARKER_HASK['<']}/span${MARKER_HASK['>']}` } for (const highlight of highlights) { const { start, end, active } = highlight code += text.substring(pos, start) const className = active ? 'ag-highlight' : 'ag-selection' let highlightContent = text.substring(start, end) if (handleLineEnding && text.endsWith('\n') && end === text.length) { highlightContent = highlightContent.substring(start, end - 1) + (escape ? getEscapeHTML('ag-line-end', '\n') : '\n') } code += escape ? getEscapeHTML(className, highlightContent) : `${highlightContent}` pos = end } if (pos !== text.length) { if (handleLineEnding && text.endsWith('\n')) { code += text.substring(pos, text.length - 1) + (escape ? getEscapeHTML('ag-line-end', '\n') : '\n') } else { code += text.substring(pos) } } return code } const hasReferenceToken = tokens => { let result = false const travel = tokens => { for (const token of tokens) { if (/reference_image|reference_link/.test(token.type)) { result = true break } if (Array.isArray(token.children) && token.children.length) { travel(token.children) } } } travel(tokens) return result } export default function renderLeafBlock (parent, block, activeBlocks, matches, useCache = false) { const { loadMathMap } = this const { cursor } = this.muya.contentState let selector = this.getSelector(block, activeBlocks) // highlight search key in block const highlights = matches.filter(m => m.key === block.key) const { text, type, checked, key, lang, functionType, editable } = block const data = { props: {}, attrs: {}, dataset: {}, style: {} } let children = '' if (text) { let tokens = [] if (highlights.length === 0 && this.tokenCache.has(text)) { tokens = this.tokenCache.get(text) } else if ( HAS_TEXT_BLOCK_REG.test(type) && functionType !== 'codeContent' && functionType !== 'languageInput' ) { const hasBeginRules = /paragraphContent|atxLine/.test(functionType) tokens = tokenizer(text, { highlights, hasBeginRules, labels: this.labels, options: this.muya.options }) const hasReferenceTokens = hasReferenceToken(tokens) if (highlights.length === 0 && useCache && DEVICE_MEMORY >= 4 && !hasReferenceTokens) { this.tokenCache.set(text, tokens) } } children = tokens.reduce((acc, token) => [...acc, ...this[snakeToCamel(token.type)](h, cursor, block, token)], []) } if (editable === false) { Object.assign(data.attrs, { spellcheck: 'false', contenteditable: 'false' }) } if (type === 'div') { const code = this.codeCache.get(block.preSibling) switch (functionType) { case 'html': { selector += `.${CLASS_OR_ID.AG_HTML_PREVIEW}` Object.assign(data.attrs, { spellcheck: 'false' }) const { disableHtml } = this.muya.options const htmlContent = sanitize(code, PREVIEW_DOMPURIFY_CONFIG, disableHtml) // handle empty html bock if (/^<([a-z][a-z\d]*)[^>]*?>(\s*)<\/\1>$/.test(htmlContent.trim())) { children = htmlToVNode('