1919 </code >
2020 </template >
2121 <template v-else-if =" part .type === ' link' " >
22- <a :href =" part.url" class =" text-accent-primary underline hover:text-accent-primary/80" target =" _blank" rel =" noopener noreferrer" >
22+ <a :href =" part.url" class =" text-accent-primary underline hover:text-accent-primary/80" target =" _blank"
23+ rel =" noopener noreferrer" >
2324 {{ part.text }}
2425 </a >
2526 </template >
2930 <template v-else-if =" part .type === ' linebreak' " >
3031 <br />
3132 </template >
33+ <template v-else-if =" part .type === ' image' " >
34+ <img :src =" part.url" :alt =" part.text || ''"
35+ class =" inline-block max-w-full h-8 mx-1 align-middle rounded hover:opacity-90 transition-opacity cursor-zoom-in" />
36+ </template >
3237 </span >
3338 </p >
3439</template >
@@ -41,7 +46,7 @@ const props = defineProps<{
4146}>()
4247
4348interface TextPart {
44- type: ' text' | ' bold' | ' italic' | ' boldItalic' | ' code' | ' link' | ' strikethrough' | ' linebreak'
49+ type: ' text' | ' bold' | ' italic' | ' boldItalic' | ' code' | ' link' | ' strikethrough' | ' linebreak' | ' image '
4550 content? : string
4651 text? : string
4752 url? : string
@@ -50,7 +55,7 @@ interface TextPart {
5055const parsedParts = computed ((): TextPart [] => {
5156 const parts: TextPart [] = []
5257 const text = props .text
53-
58+
5459 if (! text ) {
5560 return parts
5661 }
@@ -66,51 +71,54 @@ const parsedParts = computed((): TextPart[] => {
6671 const findMatches = (regex : RegExp , type : string ) => {
6772 const matches: Array <{ type: string ; start: number ; end: number ; groups: RegExpMatchArray }> = []
6873 let match: RegExpExecArray | null
69-
74+
7075 // Reset regex lastIndex
7176 regex .lastIndex = 0
72-
77+
7378 while ((match = regex .exec (text )) !== null ) {
7479 matches .push ({
7580 type ,
7681 start: match .index ,
7782 end: match .index + match [0 ].length ,
7883 groups: match
7984 })
80-
85+
8186 // Prevent infinite loop on zero-length matches
8287 if (match [0 ].length === 0 ) {
8388 regex .lastIndex ++
8489 }
8590 }
86-
91+
8792 return matches
8893 }
8994
9095 // Find all matches for each pattern type
9196 // Order matters: process more specific patterns first (e.g., boldItalic before bold/italic)
9297 const allMatches: Array <{ type: string ; start: number ; end: number ; groups: RegExpMatchArray }> = []
93-
98+
9499 // Bold+Italic (***text*** or ___text___)
95100 allMatches .push (... findMatches (/ (\*\*\* | ___)(. +? )\1 / g , ' boldItalic' ))
96-
101+
97102 // Code (inline code - process first to avoid parsing inside code blocks)
98103 allMatches .push (... findMatches (/ `([^ `\n ] + )`/ g , ' code' ))
99-
104+
100105 // Links ([text](url))
101106 allMatches .push (... findMatches (/ \[ ([^ \] ] + )\]\( ([^ )] + )\) / g , ' link' ))
102-
107+
108+ // Inline Images ()
109+ allMatches .push (... findMatches (/ !\[ ([^ \] ] * )\]\( ([^ )] + )\) / g , ' image' ))
110+
103111 // Strikethrough (~~text~~)
104112 allMatches .push (... findMatches (/ ~~(. +? )~~/ g , ' strikethrough' ))
105-
113+
106114 // Bold (**text** or __text__)
107115 // Note: We check these are not part of boldItalic by processing boldItalic first and filtering overlaps
108116 allMatches .push (... findMatches (/ (\*\* | __)([^ *_\n ] +? )\1 / g , ' bold' ))
109-
117+
110118 // Italic (*text* or _text_)
111119 // Must have word boundary to avoid matching parts of bold or code
112120 allMatches .push (... findMatches (/ (?<![*_] )(?<!\w )(\* | _)([^ *_\n ] +? )\1 (?![*_] )(?!\w )/ g , ' italic' ))
113-
121+
114122 // Line breaks (two spaces + newline or double newline)
115123 allMatches .push (... findMatches (/ \n | \n\n / g , ' linebreak' ))
116124
@@ -122,17 +130,17 @@ const parsedParts = computed((): TextPart[] => {
122130 for (let i = 0 ; i < allMatches .length ; i ++ ) {
123131 const current = allMatches [i ] as { type: string ; start: number ; end: number ; groups: RegExpMatchArray }
124132 let shouldAdd = true
125-
133+
126134 // Check if current overlaps with any already filtered match
127135 for (let j = filteredMatches .length - 1 ; j >= 0 ; j -- ) {
128136 const existing = filteredMatches [j ] as { type: string ; start: number ; end: number ; groups: RegExpMatchArray }
129137 const overlaps = ! (current .end <= existing .start || current .start >= existing .end )
130-
138+
131139 if (overlaps ) {
132140 // If current is longer, replace the existing match
133141 const currentLength = current .end - current .start
134142 const existingLength = existing .end - existing .start
135-
143+
136144 if (currentLength > existingLength ) {
137145 filteredMatches .splice (j , 1 )
138146 } else {
@@ -142,24 +150,24 @@ const parsedParts = computed((): TextPart[] => {
142150 }
143151 }
144152 }
145-
153+
146154 if (shouldAdd ) {
147155 filteredMatches .push (current )
148156 }
149157 }
150-
158+
151159 // Re-sort after filtering (positions may have changed)
152160 filteredMatches .sort ((a , b ) => a .start - b .start )
153161
154162 // Build parts array
155163 let lastPos = 0
156-
164+
157165 for (const match of filteredMatches ) {
158166 // Add text before this match
159167 if (match .start > lastPos ) {
160168 addText (text .substring (lastPos , match .start ))
161169 }
162-
170+
163171 // Add the matched part
164172 switch (match .type ) {
165173 case ' boldItalic' :
@@ -177,17 +185,20 @@ const parsedParts = computed((): TextPart[] => {
177185 case ' link' :
178186 parts .push ({ type: ' link' , text: match .groups [1 ], url: match .groups [2 ] })
179187 break
188+ case ' image' :
189+ parts .push ({ type: ' image' , text: match .groups [1 ], url: match .groups [2 ] })
190+ break
180191 case ' strikethrough' :
181192 parts .push ({ type: ' strikethrough' , content: match .groups [1 ] })
182193 break
183194 case ' linebreak' :
184195 parts .push ({ type: ' linebreak' })
185196 break
186197 }
187-
198+
188199 lastPos = match .end
189200 }
190-
201+
191202 // Add remaining text
192203 if (lastPos < text .length ) {
193204 addText (text .substring (lastPos ))
@@ -201,4 +212,3 @@ const parsedParts = computed((): TextPart[] => {
201212 return parts
202213})
203214 </script >
204-
0 commit comments