From 39cb54176efe902cc41ebdca43dd13fd973e97a0 Mon Sep 17 00:00:00 2001 From: ChrisV Date: Mon, 30 Mar 2026 02:28:10 +0900 Subject: [PATCH 1/3] =?UTF-8?q?fix:=20=E4=BF=9D=E7=95=99=20h2=20=E4=B9=8B?= =?UTF-8?q?=E5=89=8D=E7=9A=84=E5=BC=95=E8=A8=80=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/read/components/readArea/index.tsx | 77 +++++++++++++++++++++++++- services/DB.ts | 6 ++ services/MD.ts | 67 ++++++++++++++++++---- test_cheerio.js | 4 ++ test_h2.js | 23 ++++++++ test_md.js | 29 ++++++++++ test_nlp.js | 3 + test_nlp2.js | 7 +++ test_traverse.js | 35 ++++++++++++ 9 files changed, 239 insertions(+), 12 deletions(-) create mode 100644 test_cheerio.js create mode 100644 test_h2.js create mode 100644 test_md.js create mode 100644 test_nlp.js create mode 100644 test_nlp2.js create mode 100644 test_traverse.js diff --git a/app/read/components/readArea/index.tsx b/app/read/components/readArea/index.tsx index 27f0871..953a322 100644 --- a/app/read/components/readArea/index.tsx +++ b/app/read/components/readArea/index.tsx @@ -4,7 +4,7 @@ import db from "@/services/DB" import { EVENT_NAMES, EventEmitter } from "@/services/EventService" import { Radio } from "antd" import { useStyleStore, FontSize } from "@/store/useStyleStore" - +import ChatMarkdownWrapper from "@/app/components/common/MarkdownRendererWrapper" export default function ReadArea({ book, readingProgress }: { book: Book, readingProgress: ReadingProgress }) { const { fontSize } = useStyleStore() @@ -150,6 +150,17 @@ export default function ReadArea({ book, readingProgress }: { book: Book, readin ) } +// 解析图片标记:![IMG]alt|src 或 ![IMG]src +function parseImageLine(sentence: string): { src: string; alt: string } | null { + if (!sentence.startsWith('![IMG]')) return null + const content = sentence.slice(6) // 去掉 '![IMG]' + const pipeIndex = content.indexOf('|') + if (pipeIndex > 0) { + return { alt: content.slice(0, pipeIndex), src: content.slice(pipeIndex + 1) } + } + return { alt: '', src: content } +} + // 单行组件,使用memo优化性能 const Line = React.memo(({ sentence, index, isSelected, handleLineClick, setLineRef, size }: { sentence: string, @@ -162,12 +173,76 @@ const Line = React.memo(({ sentence, index, isSelected, handleLineClick, setLine if (!sentence) { return
} + + const imageInfo = parseImageLine(sentence) + const radioSizeClasses = { small: 'w-5 h-5 pt-0.5', medium: 'w-6 h-6 pt-[4.5px]', large: 'w-7 h-7 pt-[5px]' } + // 图片行 + if (imageInfo) { + return ( +
setLineRef(el, index)} + > +
handleLineClick(index)} + > + +
+
+
+ {/* eslint-disable-next-line @next/next/no-img-element */} + {imageInfo.alt} + {imageInfo.alt && ( +
{imageInfo.alt}
+ )} +
+
+ ) + } + + // Markdown (表格/代码块等) + if (sentence.startsWith('![MD]')) { + const mdContent = sentence.slice(5) // 去掉 '![MD]' + return ( +
setLineRef(el, index)} + > +
handleLineClick(index)} + > + +
+
+
+ +
+
+ ) + } + + // 普通文本行 return (
{ + // 图片段落或Markdown不拆句,整段保留 + if (paragraph.startsWith('![IMG]') || paragraph.startsWith('![MD]')) { + allSentences.push(paragraph, 'EOB') + return + } + // 判断是否主要为中文文本 const isChinese = /[\u4e00-\u9fa5]/.test(paragraph) let sentences: string[] = [] diff --git a/services/MD.ts b/services/MD.ts index 8b1cfe4..f1d491d 100644 --- a/services/MD.ts +++ b/services/MD.ts @@ -33,6 +33,21 @@ export function initMDBook(buffer: Buffer, name: string): FormattedBook { paragraphs }) } else { + // 检查第一个h2之前的内容(保留文章开头的引言/前言) + const $bodyChildren = $('body').children() + const firstH2Index = $bodyChildren.index(h2Elements.first()) + if (firstH2Index > 0) { + const beforeH2 = $bodyChildren.slice(0, firstH2Index) + const content = beforeH2.map((_, el) => $.html(el)).get().join('') + const paragraphs = extractParagraphs($, content) + if (paragraphs.length > 0) { + chapterList.push({ + title: title || '引言', + paragraphs + }) + } + } + // 根据h2元素分割内容 h2Elements.each((_, elem) => { const chapterTitle = $(elem).text() @@ -80,20 +95,50 @@ function extractParagraphs($: cheerio.CheerioAPI, htmlContent: string): string[] const $content = cheerio.load(htmlContent) const paragraphs: string[] = [] - // 提取所有段落元素 - $content('p').each((_, elem) => { - const text = $content(elem).text().trim() - if (text) { - paragraphs.push(text) + $content('body').children().each((_, elem) => { + const tagName = elem.tagName + + if (tagName === 'h1' || tagName === 'h2') { + return // 跳过(章节分割用) } - }) - // 处理其他可能的内容元素(如列表、引用等) - $content('li, blockquote').each((_, elem) => { - const text = $content(elem).text().trim() - if (text) { - paragraphs.push(text) + // 保留原始HTML供MarkdownRenderer渲染,比如表格和代码块 + if (tagName === 'table' || tagName === 'pre') { + const html = $content(elem).prop('outerHTML') || $content.html(elem) + if (html) { + paragraphs.push(`![MD]${html}`) + } + return } + + let currentText = '' + const flushText = () => { + const t = currentText.trim() + if (t) paragraphs.push(t) + currentText = '' + } + + const traverse = (node: any) => { + if (node.type === 'text') { + currentText += node.data + } else if (node.type === 'tag' && node.tagName === 'br') { + currentText += '\n' + } else if (node.type === 'tag' && node.tagName === 'img') { + flushText() + const src = node.attribs?.src + const alt = node.attribs?.alt || '' + if (src) { + paragraphs.push(`![IMG]${alt ? alt + '|' : ''}${src}`) + } + } else if (node.type === 'tag') { + if (node.children) { + node.children.forEach(traverse) + } + } + } + + traverse(elem) + flushText() }) return paragraphs diff --git a/test_cheerio.js b/test_cheerio.js new file mode 100644 index 0000000..9d942ce --- /dev/null +++ b/test_cheerio.js @@ -0,0 +1,4 @@ +const cheerio = require('cheerio'); +const $ = cheerio.load('
A
'); +const elem = $('table')[0]; +console.log($.html(elem)); diff --git a/test_h2.js b/test_h2.js new file mode 100644 index 0000000..71b899e --- /dev/null +++ b/test_h2.js @@ -0,0 +1,23 @@ +const cheerio = require('cheerio'); +const html = ` + +

Main Title

+

Introduction paragraph 1

+

Introduction paragraph 2

+

First Chapter

+

Chapter 1 content

+

Second Chapter

+

Chapter 2 content

+ +`; +const $ = cheerio.load(html); + +const h2Elements = $('h2'); +const $bodyChildren = $('body').children(); +const firstH2Index = $bodyChildren.index(h2Elements.first()); + +if (firstH2Index > 0) { + const beforeH2 = $bodyChildren.slice(0, firstH2Index); + const content = beforeH2.map((_, el) => $.html(el)).get().join(''); + console.log("BEFORE H2:\n", content); +} diff --git a/test_md.js b/test_md.js new file mode 100644 index 0000000..cde19b9 --- /dev/null +++ b/test_md.js @@ -0,0 +1,29 @@ +const MarkdownIt = require('markdown-it'); +const cheerio = require('cheerio'); +const md = new MarkdownIt(); +const text = `![OpenDev](https://substackcdn.com/image/fetch/$s_!XWY3!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd0465e70-d947-488c-9565-9924593322a9_998x477.png) +And a table: + +| Column 1 | Column 2 | +|----------|----------| +| A | B | +`; +const html = md.render(text); +console.log("HTML:", html); +const $content = cheerio.load(html); + +// mimic extractParagraphs +$content('body').children().each((_, elem) => { + const tagName = elem.tagName; + console.log("TAG:", tagName); + if (tagName === 'p') { + const imgs = $content(elem).find('img'); + console.log("P IMGS LENGTH:", imgs.length); + imgs.each((_, imgElem) => { + console.log("IMG SRC:", $content(imgElem).attr('src')); + }); + } else if (tagName === 'table') { + console.log("FOUND TABLE"); + console.log("TABLE OUTER HTML:", cheerio.html(elem)); + } +}); diff --git a/test_nlp.js b/test_nlp.js new file mode 100644 index 0000000..010a11d --- /dev/null +++ b/test_nlp.js @@ -0,0 +1,3 @@ +const nlp = require('compromise'); +const doc = nlp("![IMG]OpenDev|https://substackcdn.com/image/fetch/$s_!XWY3!,w_1456/something.png"); +console.log(doc.sentences().out('array')); diff --git a/test_nlp2.js b/test_nlp2.js new file mode 100644 index 0000000..4105da2 --- /dev/null +++ b/test_nlp2.js @@ -0,0 +1,7 @@ +const regex = /[^。!?]+[。!?]/g; +const para = "![IMG]开发测试|https://substackcdn.com/image/fetch/$s_!XWY3!,w_1456/something.png"; +const isChinese = /[\u4e00-\u9fa5]/.test(para); +console.log("isChinese:", isChinese); +let sentences = []; +if (isChinese) sentences = para.match(regex) || []; +console.log(sentences); diff --git a/test_traverse.js b/test_traverse.js new file mode 100644 index 0000000..42770fb --- /dev/null +++ b/test_traverse.js @@ -0,0 +1,35 @@ +const cheerio = require('cheerio'); +const html = `

Here is an image: img1 and some more text. Bold text.

`; +const $content = cheerio.load(html); +const paragraphs = []; + +function extractBlock(elem) { + let currentText = ''; + + function flushText() { + const t = currentText.trim(); + if (t) paragraphs.push(t); + currentText = ''; + } + + function traverse(node) { + if (node.type === 'text') { + currentText += node.data; + } else if (node.type === 'tag' && node.tagName === 'img') { + flushText(); + const src = node.attribs.src; + const alt = node.attribs.alt || ''; + if (src) paragraphs.push(`![IMG]${alt ? alt + '|' : ''}${src}`); + } else if (node.type === 'tag') { + node.children.forEach(traverse); + } + } + + traverse(elem); + flushText(); +} + +$content('body').children().each((_, elem) => { + extractBlock(elem); +}); +console.log(paragraphs); From 16d5482824b0551d8f59e77893e0fb0e8e60e639 Mon Sep 17 00:00:00 2001 From: ChrisV Date: Mon, 30 Mar 2026 02:34:33 +0900 Subject: [PATCH 2/3] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E6=89=80?= =?UTF-8?q?=E6=9C=89=E5=B1=82=E7=BA=A7=E6=A0=87=E9=A2=98=E5=88=86=E5=89=B2?= =?UTF-8?q?=E5=B9=B6=E6=B8=B2=E6=9F=93=E6=A0=91=E7=8A=B6=E7=9B=AE=E5=BD=95?= =?UTF-8?q?=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/read/components/menu.tsx | 27 +++++++++++++++---- services/BookService.ts | 3 ++- services/MD.ts | 52 +++++++++++++++++------------------- test_cheerio.js | 4 --- test_h2.js | 23 ---------------- test_md.js | 29 -------------------- test_md2.js | 34 +++++++++++++++++++++++ test_nlp.js | 3 --- test_nlp2.js | 7 ----- test_traverse.js | 35 ------------------------ types/book.ts | 2 ++ 11 files changed, 85 insertions(+), 134 deletions(-) delete mode 100644 test_cheerio.js delete mode 100644 test_h2.js delete mode 100644 test_md.js create mode 100644 test_md2.js delete mode 100644 test_nlp.js delete mode 100644 test_nlp2.js delete mode 100644 test_traverse.js diff --git a/app/read/components/menu.tsx b/app/read/components/menu.tsx index 9f65727..045850d 100644 --- a/app/read/components/menu.tsx +++ b/app/read/components/menu.tsx @@ -22,11 +22,28 @@ const renderTocMenu = ( onChapterChange: (index: number, lineIndex: number) => void, collapsed: boolean ) => { - const menuItems = toc.map(({ title, index }) => ({ - key: index, - label: collapsed ? index + 1 : title, - title: title - })) + const menuItems = toc.map(({ title, index, level }) => { + const lvl = level || 1 + const indent = collapsed ? 0 : (lvl - 1) * 12 + + return { + key: index, + label: collapsed ? index + 1 : ( +
1 ? 0.85 : 1 + }} + className="truncate" + > + {title} +
+ ), + title: title + } + }) return ( ({ title: chapter.title, - index + index, + level: chapter.level || 1 })), metadata: formattedBook.metadata } diff --git a/services/MD.ts b/services/MD.ts index f1d491d..871c7cc 100644 --- a/services/MD.ts +++ b/services/MD.ts @@ -11,61 +11,58 @@ export function initMDBook(buffer: Buffer, name: string): FormattedBook { const title = $('h1').text() || name const language = detectLanguage(mdString.slice(0, 500)) - // 将h3和h4转换为普通段落 - $('h3, h4, h5').each((_, elem) => { - const content = $(elem).html() || '' - $(elem).replaceWith(`

${content}

`) - }) - const chapterList: PlainTextChapter[] = [] - // 找到所有h2元素 - const h2Elements = $('h2') + // 找到所有顶层标题元素 (h1~h6) + const headingElements = $('body > h1, body > h2, body > h3, body > h4, body > h5, body > h6') - if (h2Elements.length === 0) { - // 如果没有h2元素,将整个内容作为一个章节 + if (headingElements.length === 0) { + // 如果没有标题元素,将整个内容作为一个章节 const content = $('body').html() || '' // 把HTML内容转换为段落数组 const paragraphs = extractParagraphs($, content) chapterList.push({ title: title, - paragraphs + paragraphs, + level: 1 }) } else { - // 检查第一个h2之前的内容(保留文章开头的引言/前言) + // 检查第一个标题之前的内容(保留文章开头的引言/前言) const $bodyChildren = $('body').children() - const firstH2Index = $bodyChildren.index(h2Elements.first()) - if (firstH2Index > 0) { - const beforeH2 = $bodyChildren.slice(0, firstH2Index) - const content = beforeH2.map((_, el) => $.html(el)).get().join('') + const firstHeadingIndex = $bodyChildren.index(headingElements.first()) + if (firstHeadingIndex > 0) { + const beforeHeading = $bodyChildren.slice(0, firstHeadingIndex) + const content = beforeHeading.map((_, el) => $.html(el)).get().join('') const paragraphs = extractParagraphs($, content) if (paragraphs.length > 0) { chapterList.push({ title: title || '引言', - paragraphs + paragraphs, + level: 1 }) } } - // 根据h2元素分割内容 - h2Elements.each((_, elem) => { + // 根据标题元素分割内容 + headingElements.each((_, elem) => { const chapterTitle = $(elem).text() + const level = parseInt(elem.tagName.replace(/h/i, ''), 10) || 1 let content = '' - // 获取当前h2元素 + // 获取当前标题元素 const $elem = $(elem) - // 获取当前h2到下一个h2之间的内容 + // 获取当前标题到下一个顶层标题之间的内容 let $nextAll = $elem.nextAll() - let $nextH2 = $nextAll.filter('h2').first() + let $nextHeading = $nextAll.filter('h1, h2, h3, h4, h5, h6').first() - if ($nextH2.length > 0) { - // 获取到下一个h2之前的所有元素 - let $contents = $nextAll.slice(0, $nextAll.index($nextH2)) + if ($nextHeading.length > 0) { + // 获取到下一个标题之前的所有元素 + let $contents = $nextAll.slice(0, $nextAll.index($nextHeading)) content = $contents.map((_, el) => $.html(el)).get().join('') } else { - // 如果没有下一个h2,获取当前h2后面的所有内容 + // 如果没有下一个标题,获取当前标题后面的所有内容 content = $nextAll.map((_, el) => $.html(el)).get().join('') } @@ -74,7 +71,8 @@ export function initMDBook(buffer: Buffer, name: string): FormattedBook { chapterList.push({ title: chapterTitle, - paragraphs + paragraphs, + level }) }) } diff --git a/test_cheerio.js b/test_cheerio.js deleted file mode 100644 index 9d942ce..0000000 --- a/test_cheerio.js +++ /dev/null @@ -1,4 +0,0 @@ -const cheerio = require('cheerio'); -const $ = cheerio.load('
A
'); -const elem = $('table')[0]; -console.log($.html(elem)); diff --git a/test_h2.js b/test_h2.js deleted file mode 100644 index 71b899e..0000000 --- a/test_h2.js +++ /dev/null @@ -1,23 +0,0 @@ -const cheerio = require('cheerio'); -const html = ` - -

Main Title

-

Introduction paragraph 1

-

Introduction paragraph 2

-

First Chapter

-

Chapter 1 content

-

Second Chapter

-

Chapter 2 content

- -`; -const $ = cheerio.load(html); - -const h2Elements = $('h2'); -const $bodyChildren = $('body').children(); -const firstH2Index = $bodyChildren.index(h2Elements.first()); - -if (firstH2Index > 0) { - const beforeH2 = $bodyChildren.slice(0, firstH2Index); - const content = beforeH2.map((_, el) => $.html(el)).get().join(''); - console.log("BEFORE H2:\n", content); -} diff --git a/test_md.js b/test_md.js deleted file mode 100644 index cde19b9..0000000 --- a/test_md.js +++ /dev/null @@ -1,29 +0,0 @@ -const MarkdownIt = require('markdown-it'); -const cheerio = require('cheerio'); -const md = new MarkdownIt(); -const text = `![OpenDev](https://substackcdn.com/image/fetch/$s_!XWY3!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd0465e70-d947-488c-9565-9924593322a9_998x477.png) -And a table: - -| Column 1 | Column 2 | -|----------|----------| -| A | B | -`; -const html = md.render(text); -console.log("HTML:", html); -const $content = cheerio.load(html); - -// mimic extractParagraphs -$content('body').children().each((_, elem) => { - const tagName = elem.tagName; - console.log("TAG:", tagName); - if (tagName === 'p') { - const imgs = $content(elem).find('img'); - console.log("P IMGS LENGTH:", imgs.length); - imgs.each((_, imgElem) => { - console.log("IMG SRC:", $content(imgElem).attr('src')); - }); - } else if (tagName === 'table') { - console.log("FOUND TABLE"); - console.log("TABLE OUTER HTML:", cheerio.html(elem)); - } -}); diff --git a/test_md2.js b/test_md2.js new file mode 100644 index 0000000..5ad9c3d --- /dev/null +++ b/test_md2.js @@ -0,0 +1,34 @@ +const cheerio = require('cheerio'); +const html = ` +

Title

+

P1

+

Subtitle 1

+

P2

+

Subsubtitle

+

P3

+

Subtitle 2

+

P4

+

Quote Heading

P5

+`; +const $ = cheerio.load(html); + +const headingElements = $('body > h1, body > h2, body > h3, body > h4, body > h5, body > h6'); +console.log("Found headings:", headingElements.length); +headingElements.each((_, elem) => { + const chapterTitle = $(elem).text(); + const level = parseInt(elem.tagName.replace('h', ''), 10); + console.log("Heading:", chapterTitle, "Level:", level); + + const $elem = $(elem); + let $nextAll = $elem.nextAll(); + let $nextHeading = $nextAll.filter('h1, h2, h3, h4, h5, h6').first(); + + let content = ''; + if ($nextHeading.length > 0) { + let $contents = $nextAll.slice(0, $nextAll.index($nextHeading)); + content = $contents.map((_, el) => $.html(el)).get().join('').trim(); + } else { + content = $nextAll.map((_, el) => $.html(el)).get().join('').trim(); + } + console.log(" Content:", content); +}); diff --git a/test_nlp.js b/test_nlp.js deleted file mode 100644 index 010a11d..0000000 --- a/test_nlp.js +++ /dev/null @@ -1,3 +0,0 @@ -const nlp = require('compromise'); -const doc = nlp("![IMG]OpenDev|https://substackcdn.com/image/fetch/$s_!XWY3!,w_1456/something.png"); -console.log(doc.sentences().out('array')); diff --git a/test_nlp2.js b/test_nlp2.js deleted file mode 100644 index 4105da2..0000000 --- a/test_nlp2.js +++ /dev/null @@ -1,7 +0,0 @@ -const regex = /[^。!?]+[。!?]/g; -const para = "![IMG]开发测试|https://substackcdn.com/image/fetch/$s_!XWY3!,w_1456/something.png"; -const isChinese = /[\u4e00-\u9fa5]/.test(para); -console.log("isChinese:", isChinese); -let sentences = []; -if (isChinese) sentences = para.match(regex) || []; -console.log(sentences); diff --git a/test_traverse.js b/test_traverse.js deleted file mode 100644 index 42770fb..0000000 --- a/test_traverse.js +++ /dev/null @@ -1,35 +0,0 @@ -const cheerio = require('cheerio'); -const html = `

Here is an image: img1 and some more text. Bold text.

`; -const $content = cheerio.load(html); -const paragraphs = []; - -function extractBlock(elem) { - let currentText = ''; - - function flushText() { - const t = currentText.trim(); - if (t) paragraphs.push(t); - currentText = ''; - } - - function traverse(node) { - if (node.type === 'text') { - currentText += node.data; - } else if (node.type === 'tag' && node.tagName === 'img') { - flushText(); - const src = node.attribs.src; - const alt = node.attribs.alt || ''; - if (src) paragraphs.push(`![IMG]${alt ? alt + '|' : ''}${src}`); - } else if (node.type === 'tag') { - node.children.forEach(traverse); - } - } - - traverse(elem); - flushText(); -} - -$content('body').children().each((_, elem) => { - extractBlock(elem); -}); -console.log(paragraphs); diff --git a/types/book.ts b/types/book.ts index a0ea932..9696d9d 100644 --- a/types/book.ts +++ b/types/book.ts @@ -27,10 +27,12 @@ export interface FormattedBook { export interface PlainTextChapter { title: string; paragraphs: string[]; + level?: number; } interface TocItem { title: string; index: number; + level?: number; } export interface Book { From f9ab5d2ebbbd3ea470024ebd22f60f4c6133a552 Mon Sep 17 00:00:00 2001 From: ChrisV Date: Mon, 30 Mar 2026 02:42:46 +0900 Subject: [PATCH 3/3] =?UTF-8?q?feat:=20=E5=9B=BE=E7=89=87=E8=A1=8C?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=E6=94=BE=E5=A4=A7=E9=95=9C=E5=9B=BE=E6=A0=87?= =?UTF-8?q?=E5=B9=B6=E6=94=AF=E6=8C=81=E5=85=A8=E5=B1=8F=E9=A2=84=E8=A7=88?= =?UTF-8?q?=E5=A4=A7=E5=9B=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/read/components/readArea/index.tsx | 31 +++++++++++++++++--------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/app/read/components/readArea/index.tsx b/app/read/components/readArea/index.tsx index 953a322..5bb861f 100644 --- a/app/read/components/readArea/index.tsx +++ b/app/read/components/readArea/index.tsx @@ -2,7 +2,8 @@ import { Book, ReadingProgress } from "@/types/book" import React, { useCallback, useEffect, useMemo, useRef, useState } from "react" import db from "@/services/DB" import { EVENT_NAMES, EventEmitter } from "@/services/EventService" -import { Radio } from "antd" +import { Radio, Image } from "antd" +import { ZoomInOutlined } from "@ant-design/icons" import { useStyleStore, FontSize } from "@/store/useStyleStore" import ChatMarkdownWrapper from "@/app/components/common/MarkdownRendererWrapper" @@ -170,6 +171,8 @@ const Line = React.memo(({ sentence, index, isSelected, handleLineClick, setLine setLineRef: (element: HTMLDivElement | null, index: number) => void, size: FontSize }) => { + const [previewVisible, setPreviewVisible] = useState(false) + if (!sentence) { return
} @@ -191,23 +194,31 @@ const Line = React.memo(({ sentence, index, isSelected, handleLineClick, setLine >
handleLineClick(index)} > - +
{ + e.stopPropagation() + setPreviewVisible(true) + }} + title="查看大图" + > + +
- {/* eslint-disable-next-line @next/next/no-img-element */} - {imageInfo.alt} setPreviewVisible(val), + mask:
查看大图
+ }} /> {imageInfo.alt && (
{imageInfo.alt}