mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-21 16:01:35 +08:00
Remove verbose logging and simplify YAML front matter handling
- Eliminate logger imports and debug statements throughout YAML front matter extension - Streamline markdown tokenizer to match YAML front matter with optional leading whitespace - Simplify parseMarkdown and renderMarkdown logic using helper methods - Remove custom YAML front matter plugin from markdown converter as it's now handled by TipTap extension
This commit is contained in:
parent
ef7e8a7201
commit
cfb9ee7df3
@ -1,11 +1,8 @@
|
|||||||
import { loggerService } from '@logger'
|
|
||||||
import { mergeAttributes, Node } from '@tiptap/core'
|
import { mergeAttributes, Node } from '@tiptap/core'
|
||||||
import { ReactNodeViewRenderer } from '@tiptap/react'
|
import { ReactNodeViewRenderer } from '@tiptap/react'
|
||||||
|
|
||||||
import YamlFrontMatterNodeView from '../components/YamlFrontMatterNodeView'
|
import YamlFrontMatterNodeView from '../components/YamlFrontMatterNodeView'
|
||||||
|
|
||||||
const logger = loggerService.withContext('YamlFrontMatterExtension')
|
|
||||||
|
|
||||||
declare module '@tiptap/core' {
|
declare module '@tiptap/core' {
|
||||||
interface Commands<ReturnType> {
|
interface Commands<ReturnType> {
|
||||||
yamlFrontMatter: {
|
yamlFrontMatter: {
|
||||||
@ -24,30 +21,17 @@ export const YamlFrontMatter = Node.create({
|
|||||||
markdownTokenizer: {
|
markdownTokenizer: {
|
||||||
name: 'yamlFrontMatter',
|
name: 'yamlFrontMatter',
|
||||||
level: 'block',
|
level: 'block',
|
||||||
// Optimization: check if content starts with ---
|
|
||||||
start(src: string) {
|
|
||||||
logger.info('🔍 Tokenizer start() called', {
|
|
||||||
srcLength: src.length,
|
|
||||||
srcPrefix: src.substring(0, 60).replace(/\n/g, '\\n'),
|
|
||||||
startsWithDashes: src.startsWith('---\n')
|
|
||||||
})
|
|
||||||
|
|
||||||
const result = src.match(/^---\n/) ? 0 : -1
|
start(src: string) {
|
||||||
logger.info('✅ Tokenizer start() result:', { result })
|
const result = src.match(/^\s*---\n/) ? 0 : -1
|
||||||
return result
|
return result
|
||||||
},
|
},
|
||||||
// Parse YAML front matter
|
// Parse YAML front matter
|
||||||
tokenize(src: string) {
|
tokenize(src: string) {
|
||||||
logger.info('🔍 Tokenizer tokenize() called', {
|
|
||||||
srcLength: src.length,
|
|
||||||
srcPrefix: src.substring(0, 120).replace(/\n/g, '\\n')
|
|
||||||
})
|
|
||||||
|
|
||||||
// Match: ---\n...yaml content...\n---
|
// Match: ---\n...yaml content...\n---
|
||||||
const match = /^---\n([\s\S]*?)\n---(?:\n|$)/.exec(src)
|
const match = /^---\n([\s\S]*?)\n---(?:\n|$)/.exec(src)
|
||||||
|
|
||||||
if (!match) {
|
if (!match) {
|
||||||
logger.warn('❌ Tokenizer tokenize() - NO MATCH FOUND')
|
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,74 +40,37 @@ export const YamlFrontMatter = Node.create({
|
|||||||
raw: match[0],
|
raw: match[0],
|
||||||
text: match[1] // YAML content without delimiters
|
text: match[1] // YAML content without delimiters
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info('✅ Tokenizer tokenize() - MATCH FOUND', {
|
|
||||||
rawLength: token.raw.length,
|
|
||||||
textLength: token.text.length,
|
|
||||||
textPreview: token.text.substring(0, 100).replace(/\n/g, '\\n')
|
|
||||||
})
|
|
||||||
|
|
||||||
return token
|
return token
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Parse markdown token to Tiptap JSON
|
// Parse markdown token to Tiptap JSON
|
||||||
parseMarkdown(token, helpers) {
|
parseMarkdown(token, helpers) {
|
||||||
logger.info('🔍 parseMarkdown() called', {
|
const attrs = {
|
||||||
tokenType: token.type,
|
content: token.text || ''
|
||||||
hasText: !!token.text,
|
|
||||||
textLength: token.text?.length || 0,
|
|
||||||
textPreview: token.text?.substring(0, 100).replace(/\n/g, '\\n'),
|
|
||||||
hasTokens: !!token.tokens,
|
|
||||||
tokensLength: token.tokens?.length || 0
|
|
||||||
})
|
|
||||||
|
|
||||||
// Since this is an atom node, we don't need child content
|
|
||||||
const result = {
|
|
||||||
type: 'yamlFrontMatter', // Use explicit node name instead of this.name
|
|
||||||
attrs: {
|
|
||||||
content: token.text || ''
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info('✅ parseMarkdown() result', {
|
return helpers.createNode('yamlFrontMatter', attrs)
|
||||||
type: result.type,
|
|
||||||
contentLength: result.attrs.content.length
|
|
||||||
})
|
|
||||||
|
|
||||||
return result
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Serialize Tiptap node to markdown
|
// Serialize Tiptap node to markdown
|
||||||
renderMarkdown(node) {
|
renderMarkdown(node) {
|
||||||
logger.info('🔍 renderMarkdown() called', {
|
|
||||||
nodeType: node.type,
|
|
||||||
hasContent: !!node.attrs?.content,
|
|
||||||
contentLength: node.attrs?.content?.length || 0,
|
|
||||||
contentPreview: node.attrs?.content?.substring(0, 100).replace(/\n/g, '\\n')
|
|
||||||
})
|
|
||||||
|
|
||||||
const content = node.attrs?.content || ''
|
const content = node.attrs?.content || ''
|
||||||
if (!content.trim()) {
|
if (!content.trim()) {
|
||||||
logger.info('⚠️ renderMarkdown() - empty content, returning empty string')
|
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
|
|
||||||
const trimmedContent = content.trim()
|
|
||||||
let result = ''
|
let result = ''
|
||||||
|
|
||||||
// Ensure proper format with closing ---
|
// Ensure proper format with opening and closing ---
|
||||||
if (trimmedContent.endsWith('---')) {
|
// The content is stored without the --- delimiters, so we need to add them back
|
||||||
result = trimmedContent + '\n\n'
|
if (content.endsWith('---')) {
|
||||||
|
// Content already has closing ---, just add opening
|
||||||
|
result = '---\n' + content + '\n\n'
|
||||||
} else {
|
} else {
|
||||||
result = trimmedContent + '\n---\n\n'
|
// Add both opening and closing ---
|
||||||
|
result = '---\n' + content + '\n---\n\n'
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info('✅ renderMarkdown() result', {
|
|
||||||
resultLength: result.length,
|
|
||||||
resultPreview: result.substring(0, 120).replace(/\n/g, '\\n')
|
|
||||||
})
|
|
||||||
|
|
||||||
return result
|
return result
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import { loggerService } from '@logger'
|
import { loggerService } from '@logger'
|
||||||
import { MARKDOWN_SOURCE_LINE_ATTR } from '@renderer/components/RichEditor/constants'
|
import { MARKDOWN_SOURCE_LINE_ATTR } from '@renderer/components/RichEditor/constants'
|
||||||
import he from 'he'
|
|
||||||
import MarkdownIt from 'markdown-it'
|
import MarkdownIt from 'markdown-it'
|
||||||
import TurndownService from 'turndown'
|
import TurndownService from 'turndown'
|
||||||
|
|
||||||
@ -58,141 +57,6 @@ defaultBlockRules.forEach((ruleName) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Override the code_block and code_inline renderers to properly escape HTML entities
|
|
||||||
md.renderer.rules.code_block = function (tokens, idx) {
|
|
||||||
const token = tokens[idx]
|
|
||||||
const langName = token.info ? ` class="language-${token.info.trim()}"` : ''
|
|
||||||
const escapedContent = he.encode(token.content, { useNamedReferences: false })
|
|
||||||
let html = `<pre><code${langName}>${escapedContent}</code></pre>`
|
|
||||||
html = injectLineNumber(token, html)
|
|
||||||
return html
|
|
||||||
}
|
|
||||||
|
|
||||||
md.renderer.rules.code_inline = function (tokens, idx) {
|
|
||||||
const token = tokens[idx]
|
|
||||||
const escapedContent = he.encode(token.content, { useNamedReferences: false })
|
|
||||||
return `<code>${escapedContent}</code>`
|
|
||||||
}
|
|
||||||
|
|
||||||
md.renderer.rules.fence = function (tokens, idx) {
|
|
||||||
const token = tokens[idx]
|
|
||||||
const langName = token.info ? ` class="language-${token.info.trim()}"` : ''
|
|
||||||
const escapedContent = he.encode(token.content, { useNamedReferences: false })
|
|
||||||
let html = `<pre><code${langName}>${escapedContent}</code></pre>`
|
|
||||||
html = injectLineNumber(token, html)
|
|
||||||
return html
|
|
||||||
}
|
|
||||||
|
|
||||||
interface TokenLike {
|
|
||||||
content: string
|
|
||||||
block?: boolean
|
|
||||||
map?: [number, number]
|
|
||||||
}
|
|
||||||
|
|
||||||
interface BlockStateLike {
|
|
||||||
src: string
|
|
||||||
bMarks: number[]
|
|
||||||
eMarks: number[]
|
|
||||||
tShift: number[]
|
|
||||||
line: number
|
|
||||||
parentType: string
|
|
||||||
blkIndent: number
|
|
||||||
push: (type: string, tag: string, nesting: number) => TokenLike
|
|
||||||
}
|
|
||||||
|
|
||||||
interface InlineStateLike {
|
|
||||||
src: string
|
|
||||||
pos: number
|
|
||||||
posMax: number
|
|
||||||
push: (type: string, tag: string, nesting: number) => TokenLike & { content?: string }
|
|
||||||
}
|
|
||||||
|
|
||||||
function yamlFrontMatterPlugin(md: MarkdownIt) {
|
|
||||||
// Parser: recognize YAML front matter
|
|
||||||
md.block.ruler.before(
|
|
||||||
'table',
|
|
||||||
'yaml_front_matter',
|
|
||||||
(stateLike: unknown, startLine: number, endLine: number, silent: boolean): boolean => {
|
|
||||||
const state = stateLike as BlockStateLike
|
|
||||||
|
|
||||||
// Only check at the very beginning of the document
|
|
||||||
if (startLine !== 0) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
const startPos = state.bMarks[startLine] + state.tShift[startLine]
|
|
||||||
const maxPos = state.eMarks[startLine]
|
|
||||||
|
|
||||||
// Must begin with --- at document start
|
|
||||||
if (startPos + 3 > maxPos) return false
|
|
||||||
if (
|
|
||||||
state.src.charCodeAt(startPos) !== 0x2d /* - */ ||
|
|
||||||
state.src.charCodeAt(startPos + 1) !== 0x2d /* - */ ||
|
|
||||||
state.src.charCodeAt(startPos + 2) !== 0x2d /* - */
|
|
||||||
) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// If requested only to validate existence
|
|
||||||
if (silent) return true
|
|
||||||
|
|
||||||
// Search for closing ---
|
|
||||||
let nextLine = startLine + 1
|
|
||||||
let found = false
|
|
||||||
|
|
||||||
for (nextLine = startLine + 1; nextLine < endLine; nextLine++) {
|
|
||||||
const lineStart = state.bMarks[nextLine] + state.tShift[nextLine]
|
|
||||||
const lineEnd = state.eMarks[nextLine]
|
|
||||||
const line = state.src.slice(lineStart, lineEnd).trim()
|
|
||||||
|
|
||||||
if (line === '---') {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!found) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract YAML content between the --- delimiters, preserving original indentation
|
|
||||||
const yamlLines: string[] = []
|
|
||||||
for (let lineIdx = startLine + 1; lineIdx < nextLine; lineIdx++) {
|
|
||||||
// Use the original line markers without shift to preserve indentation
|
|
||||||
const lineStart = state.bMarks[lineIdx]
|
|
||||||
const lineEnd = state.eMarks[lineIdx]
|
|
||||||
yamlLines.push(state.src.slice(lineStart, lineEnd))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Also capture the closing --- line with its indentation
|
|
||||||
const closingLineStart = state.bMarks[nextLine]
|
|
||||||
const closingLineEnd = state.eMarks[nextLine]
|
|
||||||
const closingLine = state.src.slice(closingLineStart, closingLineEnd)
|
|
||||||
|
|
||||||
const yamlContent = yamlLines.join('\n') + '\n' + closingLine
|
|
||||||
|
|
||||||
const token = state.push('yaml_front_matter', 'div', 0)
|
|
||||||
token.block = true
|
|
||||||
token.map = [startLine, nextLine + 1]
|
|
||||||
token.content = yamlContent
|
|
||||||
|
|
||||||
state.line = nextLine + 1
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// Renderer: output YAML front matter as special HTML element
|
|
||||||
md.renderer.rules.yaml_front_matter = (tokens: Array<{ content?: string }>, idx: number): string => {
|
|
||||||
const token = tokens[idx]
|
|
||||||
const content = token?.content ?? ''
|
|
||||||
let html = `<div data-type="yaml-front-matter" data-content="${he.encode(content)}">${content}</div>`
|
|
||||||
html = injectLineNumber(token, html)
|
|
||||||
return html
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
md.use(yamlFrontMatterPlugin)
|
|
||||||
|
|
||||||
// Initialize turndown service
|
// Initialize turndown service
|
||||||
const turndownService = new TurndownService({
|
const turndownService = new TurndownService({
|
||||||
headingStyle: 'atx', // Use # for headings
|
headingStyle: 'atx', // Use # for headings
|
||||||
@ -204,28 +68,6 @@ const turndownService = new TurndownService({
|
|||||||
strongDelimiter: '**' // Use ** for strong
|
strongDelimiter: '**' // Use ** for strong
|
||||||
})
|
})
|
||||||
|
|
||||||
// Custom rule to preserve YAML front matter
|
|
||||||
turndownService.addRule('yamlFrontMatter', {
|
|
||||||
filter: (node: Element) => {
|
|
||||||
return node.nodeName === 'DIV' && node.getAttribute?.('data-type') === 'yaml-front-matter'
|
|
||||||
},
|
|
||||||
replacement: (_content: string, node: Node) => {
|
|
||||||
const element = node as Element
|
|
||||||
const yamlContent = element.getAttribute?.('data-content') || ''
|
|
||||||
const decodedContent = he.decode(yamlContent, {
|
|
||||||
isAttributeValue: false,
|
|
||||||
strict: false
|
|
||||||
})
|
|
||||||
// The decodedContent already includes the complete YAML with closing ---
|
|
||||||
// We just need to add the opening --- if it's not there
|
|
||||||
if (decodedContent.startsWith('---')) {
|
|
||||||
return decodedContent
|
|
||||||
} else {
|
|
||||||
return `---\n${decodedContent}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets plain text preview from Markdown content
|
* Gets plain text preview from Markdown content
|
||||||
* @param markdown - Markdown string
|
* @param markdown - Markdown string
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user