@@ -37,6 +37,32 @@ function generateHtmlBlockId(): string {
3737 return `hb_${ Date . now ( ) } _${ Math . random ( ) . toString ( 36 ) . substr ( 2 , 9 ) } ` ;
3838}
3939
40+ function generateConsecutiveImageGroupId ( ) : string {
41+ return `cig_${ Date . now ( ) } _${ Math . random ( ) . toString ( 36 ) . slice ( 2 , 11 ) } ` ;
42+ }
43+
44+ function buildImageMarkdown ( image : ProseMirrorNode ) : string {
45+ const alt = image . attrs . alt || "" ;
46+ const src = image . attrs . src || "" ;
47+ const title = image . attrs . title || "" ;
48+ const linkHref = image . attrs . linkHref || "" ;
49+ const linkTitle = image . attrs . linkTitle || "" ;
50+ const titlePart = title ? ` "${ title } "` : "" ;
51+ const imgMarkdown = `` ;
52+
53+ if ( linkHref ) {
54+ const linkTitlePart = linkTitle ? ` "${ linkTitle } "` : "" ;
55+ return `[${ imgMarkdown } ](${ linkHref } ${ linkTitlePart } )` ;
56+ }
57+
58+ return imgMarkdown ;
59+ }
60+
61+ const IMAGE_TOKEN_PATTERNS = {
62+ linked : / \[ ! \[ ( [ ^ \] ] * ) \] \( ( .+ ?) (?: \s + " ( [ ^ " ] * ) " ) ? \) \] \( ( .+ ?) (?: \s + " ( [ ^ " ] * ) " ) ? \) / y,
63+ normal : / ! \[ ( [ ^ \] ] * ) \] \( ( .+ ?) (?: \s + " ( [ ^ " ] * ) " ) ? \) / y,
64+ } ;
65+
4066/**
4167 * 将代码块转换为多个段落节点
4268 */
@@ -121,22 +147,22 @@ function transformImageToParagraph(image: ProseMirrorNode, schema: Schema): Pros
121147 const title = image . attrs . title || "" ;
122148 const linkHref = image . attrs . linkHref || "" ;
123149 const linkTitle = image . attrs . linkTitle || "" ;
124- const titlePart = title ? ` "${ title } "` : "" ;
125- const imgMarkdown = `` ;
126- let markdownText : string ;
127- if ( linkHref ) {
128- const linkTitlePart = linkTitle ? ` "${ linkTitle } "` : "" ;
129- markdownText = `[${ imgMarkdown } ](${ linkHref } ${ linkTitlePart } )` ;
130- } else {
131- markdownText = imgMarkdown ;
132- }
133-
134150 return schema . nodes . paragraph . create (
135151 { imageAttrs : { src, alt, title, linkHref, linkTitle } } ,
136- schema . text ( markdownText )
152+ schema . text ( buildImageMarkdown ( image ) )
137153 ) ;
138154}
139155
156+ function transformImageGroupToParagraph (
157+ images : ProseMirrorNode [ ] ,
158+ schema : Schema
159+ ) : ProseMirrorNode | null {
160+ if ( images . length === 0 ) return null ;
161+
162+ const markdownText = images . map ( ( image ) => buildImageMarkdown ( image ) ) . join ( "" ) ;
163+ return schema . nodes . paragraph . create ( { imageGroupSource : true } , schema . text ( markdownText ) ) ;
164+ }
165+
140166/**
141167 * 将图片段落节点转换回图片节点
142168 */
@@ -178,6 +204,62 @@ function transformParagraphToImage(
178204 return null ;
179205}
180206
207+ function transformParagraphToImages (
208+ paragraph : ProseMirrorNode ,
209+ schema : Schema
210+ ) : ProseMirrorNode [ ] | null {
211+ if ( ! paragraph . attrs . imageGroupSource ) return null ;
212+
213+ const images : ProseMirrorNode [ ] = [ ] ;
214+ const text = paragraph . textContent ;
215+ let index = 0 ;
216+ const groupId = generateConsecutiveImageGroupId ( ) ;
217+
218+ while ( index < text . length ) {
219+ while ( index < text . length && / \s / . test ( text [ index ] ) ) {
220+ index ++ ;
221+ }
222+
223+ if ( index >= text . length ) break ;
224+
225+ IMAGE_TOKEN_PATTERNS . linked . lastIndex = index ;
226+ let match = IMAGE_TOKEN_PATTERNS . linked . exec ( text ) ;
227+ if ( match ) {
228+ images . push (
229+ schema . nodes . image . create ( {
230+ alt : match [ 1 ] || "" ,
231+ src : match [ 2 ] || "" ,
232+ title : match [ 3 ] || "" ,
233+ linkHref : match [ 4 ] || "" ,
234+ linkTitle : match [ 5 ] || "" ,
235+ consecutiveGroup : groupId ,
236+ } )
237+ ) ;
238+ index = IMAGE_TOKEN_PATTERNS . linked . lastIndex ;
239+ continue ;
240+ }
241+
242+ IMAGE_TOKEN_PATTERNS . normal . lastIndex = index ;
243+ match = IMAGE_TOKEN_PATTERNS . normal . exec ( text ) ;
244+ if ( match ) {
245+ images . push (
246+ schema . nodes . image . create ( {
247+ alt : match [ 1 ] || "" ,
248+ src : match [ 2 ] || "" ,
249+ title : match [ 3 ] || "" ,
250+ consecutiveGroup : groupId ,
251+ } )
252+ ) ;
253+ index = IMAGE_TOKEN_PATTERNS . normal . lastIndex ;
254+ continue ;
255+ }
256+
257+ return null ;
258+ }
259+
260+ return images . length > 0 ? images : null ;
261+ }
262+
181263/**
182264 * 将分割线节点转换为段落节点
183265 */
@@ -522,9 +604,39 @@ function processNodeForSourceConversion(
522604 // 递归处理子节点
523605 if ( node . content . size > 0 ) {
524606 const newChildren : ProseMirrorNode [ ] = [ ] ;
607+ let consecutiveImageGroup : ProseMirrorNode [ ] = [ ] ;
608+ let currentConsecutiveImageGroupId : string | null = null ;
525609 let changed = false ;
526610
611+ const flushConsecutiveImageGroup = ( ) => {
612+ if ( consecutiveImageGroup . length === 0 ) return ;
613+ const paragraph = transformImageGroupToParagraph ( consecutiveImageGroup , schema ) ;
614+ if ( paragraph ) {
615+ newChildren . push ( paragraph ) ;
616+ changed = true ;
617+ } else {
618+ consecutiveImageGroup . forEach ( ( image ) =>
619+ newChildren . push ( transformImageToParagraph ( image , schema ) )
620+ ) ;
621+ changed = true ;
622+ }
623+ consecutiveImageGroup = [ ] ;
624+ currentConsecutiveImageGroupId = null ;
625+ } ;
626+
527627 node . content . forEach ( ( child ) => {
628+ if ( child . type . name === "image" && child . attrs . consecutiveGroup ) {
629+ const groupId = child . attrs . consecutiveGroup as string ;
630+ if ( currentConsecutiveImageGroupId && currentConsecutiveImageGroupId !== groupId ) {
631+ flushConsecutiveImageGroup ( ) ;
632+ }
633+ currentConsecutiveImageGroupId = groupId ;
634+ consecutiveImageGroup . push ( child ) ;
635+ return ;
636+ }
637+
638+ flushConsecutiveImageGroup ( ) ;
639+
528640 const processed = processNodeForSourceConversion ( child , schema ) ;
529641 if ( Array . isArray ( processed ) ) {
530642 newChildren . push ( ...processed ) ;
@@ -537,6 +649,8 @@ function processNodeForSourceConversion(
537649 }
538650 } ) ;
539651
652+ flushConsecutiveImageGroup ( ) ;
653+
540654 if ( changed ) {
541655 return node . type . create ( node . attrs , Fragment . from ( newChildren ) , node . marks ) ;
542656 }
@@ -553,9 +667,39 @@ export function convertBlocksToParagraphs(tr: Transaction): Transaction {
553667 const doc = tr . doc ;
554668 const schema = doc . type . schema ;
555669 const newContent : ProseMirrorNode [ ] = [ ] ;
670+ let consecutiveImageGroup : ProseMirrorNode [ ] = [ ] ;
671+ let currentConsecutiveImageGroupId : string | null = null ;
556672 let changed = false ;
557673
674+ const flushConsecutiveImageGroup = ( ) => {
675+ if ( consecutiveImageGroup . length === 0 ) return ;
676+ const paragraph = transformImageGroupToParagraph ( consecutiveImageGroup , schema ) ;
677+ if ( paragraph ) {
678+ newContent . push ( paragraph ) ;
679+ changed = true ;
680+ } else {
681+ consecutiveImageGroup . forEach ( ( image ) =>
682+ newContent . push ( transformImageToParagraph ( image , schema ) )
683+ ) ;
684+ changed = true ;
685+ }
686+ consecutiveImageGroup = [ ] ;
687+ currentConsecutiveImageGroupId = null ;
688+ } ;
689+
558690 doc . forEach ( ( node ) => {
691+ if ( node . type . name === "image" && node . attrs . consecutiveGroup ) {
692+ const groupId = node . attrs . consecutiveGroup as string ;
693+ if ( currentConsecutiveImageGroupId && currentConsecutiveImageGroupId !== groupId ) {
694+ flushConsecutiveImageGroup ( ) ;
695+ }
696+ currentConsecutiveImageGroupId = groupId ;
697+ consecutiveImageGroup . push ( node ) ;
698+ return ;
699+ }
700+
701+ flushConsecutiveImageGroup ( ) ;
702+
559703 const processed = processNodeForSourceConversion ( node , schema ) ;
560704 if ( Array . isArray ( processed ) ) {
561705 newContent . push ( ...processed ) ;
@@ -566,6 +710,8 @@ export function convertBlocksToParagraphs(tr: Transaction): Transaction {
566710 }
567711 } ) ;
568712
713+ flushConsecutiveImageGroup ( ) ;
714+
569715 if ( changed && newContent . length > 0 ) {
570716 const step = new ReplaceStep ( 0 , doc . content . size , new Slice ( Fragment . from ( newContent ) , 0 , 0 ) ) ;
571717 tr . step ( step ) ;
@@ -745,7 +891,10 @@ function processNodeForBlockConversion(
745891 flushMathBlockGroup ( ) ;
746892 flushListGroup ( ) ;
747893
748- if ( child . attrs . imageAttrs ) {
894+ if ( child . attrs . imageGroupSource ) {
895+ const images = transformParagraphToImages ( child , schema ) ;
896+ newChildren . push ( ...( images || [ child ] ) ) ;
897+ } else if ( child . attrs . imageAttrs ) {
749898 const image = transformParagraphToImage ( child , schema ) ;
750899 newChildren . push ( image || child ) ;
751900 } else if ( child . attrs . hrSource ) {
@@ -963,7 +1112,10 @@ export function convertParagraphsToBlocks(tr: Transaction): Transaction {
9631112 flushMathBlockGroup ( ) ;
9641113 flushListGroup ( ) ;
9651114
966- if ( node . attrs . imageAttrs ) {
1115+ if ( node . attrs . imageGroupSource ) {
1116+ const images = transformParagraphToImages ( node , schema ) ;
1117+ newContent . push ( ...( images || [ node ] ) ) ;
1118+ } else if ( node . attrs . imageAttrs ) {
9671119 // 图片段落
9681120 const image = transformParagraphToImage ( node , schema ) ;
9691121 newContent . push ( image || node ) ;
0 commit comments