@@ -52,12 +52,12 @@ function isListChild(n: Node): boolean {
5252 return LIST_TYPES . includes ( n . parentNode ?. nodeName ) ;
5353}
5454
55- function parseAtRoomMentions ( text : string , pc : PartCreator , shouldEscape = true ) : Part [ ] {
55+ function parseAtRoomMentions ( text : string , pc : PartCreator , opts : IParseOptions ) : Part [ ] {
5656 const ATROOM = "@room" ;
5757 const parts : Part [ ] = [ ] ;
5858 text . split ( ATROOM ) . forEach ( ( textPart , i , arr ) => {
5959 if ( textPart . length ) {
60- parts . push ( ...pc . plainWithEmoji ( shouldEscape ? escape ( textPart ) : textPart ) ) ;
60+ parts . push ( ...pc . plainWithEmoji ( opts . shouldEscape ? escape ( textPart ) : textPart ) ) ;
6161 }
6262 // it's safe to never append @room after the last textPart
6363 // as split will report an empty string at the end if
@@ -70,7 +70,7 @@ function parseAtRoomMentions(text: string, pc: PartCreator, shouldEscape = true)
7070 return parts ;
7171}
7272
73- function parseLink ( n : Node , pc : PartCreator ) : Part [ ] {
73+ function parseLink ( n : Node , pc : PartCreator , opts : IParseOptions ) : Part [ ] {
7474 const { href } = n as HTMLAnchorElement ;
7575 const resourceId = getPrimaryPermalinkEntity ( href ) ; // The room/user ID
7676
@@ -81,18 +81,18 @@ function parseLink(n: Node, pc: PartCreator): Part[] {
8181
8282 const children = Array . from ( n . childNodes ) ;
8383 if ( href === n . textContent && children . every ( c => c . nodeType === Node . TEXT_NODE ) ) {
84- return parseAtRoomMentions ( n . textContent , pc ) ;
84+ return parseAtRoomMentions ( n . textContent , pc , opts ) ;
8585 } else {
86- return [ pc . plain ( "[" ) , ...parseChildren ( n , pc ) , pc . plain ( `](${ href } )` ) ] ;
86+ return [ pc . plain ( "[" ) , ...parseChildren ( n , pc , opts ) , pc . plain ( `](${ href } )` ) ] ;
8787 }
8888}
8989
90- function parseImage ( n : Node , pc : PartCreator ) : Part [ ] {
90+ function parseImage ( n : Node , pc : PartCreator , opts : IParseOptions ) : Part [ ] {
9191 const { alt, src } = n as HTMLImageElement ;
9292 return pc . plainWithEmoji ( `` ) ;
9393}
9494
95- function parseCodeBlock ( n : Node , pc : PartCreator ) : Part [ ] {
95+ function parseCodeBlock ( n : Node , pc : PartCreator , opts : IParseOptions ) : Part [ ] {
9696 let language = "" ;
9797 if ( n . firstChild ?. nodeName === "CODE" ) {
9898 for ( const className of ( n . firstChild as HTMLElement ) . classList ) {
@@ -117,10 +117,10 @@ function parseCodeBlock(n: Node, pc: PartCreator): Part[] {
117117 return parts ;
118118}
119119
120- function parseHeader ( n : Node , pc : PartCreator ) : Part [ ] {
120+ function parseHeader ( n : Node , pc : PartCreator , opts : IParseOptions ) : Part [ ] {
121121 const depth = parseInt ( n . nodeName . slice ( 1 ) , 10 ) ;
122122 const prefix = pc . plain ( "#" . repeat ( depth ) + " " ) ;
123- return [ prefix , ...parseChildren ( n , pc ) ] ;
123+ return [ prefix , ...parseChildren ( n , pc , opts ) ] ;
124124}
125125
126126function checkIgnored ( n ) {
@@ -144,10 +144,10 @@ function prefixLines(parts: Part[], prefix: string, pc: PartCreator) {
144144 }
145145}
146146
147- function parseChildren ( n : Node , pc : PartCreator , mkListItem ?: ( li : Node ) => Part [ ] ) : Part [ ] {
147+ function parseChildren ( n : Node , pc : PartCreator , opts : IParseOptions , mkListItem ?: ( li : Node ) => Part [ ] ) : Part [ ] {
148148 let prev ;
149149 return Array . from ( n . childNodes ) . flatMap ( c => {
150- const parsed = parseNode ( c , pc , mkListItem ) ;
150+ const parsed = parseNode ( c , pc , opts , mkListItem ) ;
151151 if ( parsed . length && prev && ( checkBlockNode ( prev ) || checkBlockNode ( c ) ) ) {
152152 if ( isListChild ( c ) ) {
153153 // Use tighter spacing within lists
@@ -161,12 +161,12 @@ function parseChildren(n: Node, pc: PartCreator, mkListItem?: (li: Node) => Part
161161 } ) ;
162162}
163163
164- function parseNode ( n : Node , pc : PartCreator , mkListItem ?: ( li : Node ) => Part [ ] ) : Part [ ] {
164+ function parseNode ( n : Node , pc : PartCreator , opts : IParseOptions , mkListItem ?: ( li : Node ) => Part [ ] ) : Part [ ] {
165165 if ( checkIgnored ( n ) ) return [ ] ;
166166
167167 switch ( n . nodeType ) {
168168 case Node . TEXT_NODE :
169- return parseAtRoomMentions ( n . nodeValue , pc ) ;
169+ return parseAtRoomMentions ( n . nodeValue , pc , opts ) ;
170170 case Node . ELEMENT_NODE :
171171 switch ( n . nodeName ) {
172172 case "H1" :
@@ -175,52 +175,52 @@ function parseNode(n: Node, pc: PartCreator, mkListItem?: (li: Node) => Part[]):
175175 case "H4" :
176176 case "H5" :
177177 case "H6" :
178- return parseHeader ( n , pc ) ;
178+ return parseHeader ( n , pc , opts ) ;
179179 case "A" :
180- return parseLink ( n , pc ) ;
180+ return parseLink ( n , pc , opts ) ;
181181 case "IMG" :
182- return parseImage ( n , pc ) ;
182+ return parseImage ( n , pc , opts ) ;
183183 case "BR" :
184184 return [ pc . newline ( ) ] ;
185185 case "HR" :
186186 return [ pc . plain ( "---" ) ] ;
187187 case "EM" :
188- return [ pc . plain ( "_" ) , ...parseChildren ( n , pc ) , pc . plain ( "_" ) ] ;
188+ return [ pc . plain ( "_" ) , ...parseChildren ( n , pc , opts ) , pc . plain ( "_" ) ] ;
189189 case "STRONG" :
190- return [ pc . plain ( "**" ) , ...parseChildren ( n , pc ) , pc . plain ( "**" ) ] ;
190+ return [ pc . plain ( "**" ) , ...parseChildren ( n , pc , opts ) , pc . plain ( "**" ) ] ;
191191 case "DEL" :
192- return [ pc . plain ( "<del>" ) , ...parseChildren ( n , pc ) , pc . plain ( "</del>" ) ] ;
192+ return [ pc . plain ( "<del>" ) , ...parseChildren ( n , pc , opts ) , pc . plain ( "</del>" ) ] ;
193193 case "SUB" :
194- return [ pc . plain ( "<sub>" ) , ...parseChildren ( n , pc ) , pc . plain ( "</sub>" ) ] ;
194+ return [ pc . plain ( "<sub>" ) , ...parseChildren ( n , pc , opts ) , pc . plain ( "</sub>" ) ] ;
195195 case "SUP" :
196- return [ pc . plain ( "<sup>" ) , ...parseChildren ( n , pc ) , pc . plain ( "</sup>" ) ] ;
196+ return [ pc . plain ( "<sup>" ) , ...parseChildren ( n , pc , opts ) , pc . plain ( "</sup>" ) ] ;
197197 case "U" :
198- return [ pc . plain ( "<u>" ) , ...parseChildren ( n , pc ) , pc . plain ( "</u>" ) ] ;
198+ return [ pc . plain ( "<u>" ) , ...parseChildren ( n , pc , opts ) , pc . plain ( "</u>" ) ] ;
199199 case "PRE" :
200- return parseCodeBlock ( n , pc ) ;
200+ return parseCodeBlock ( n , pc , opts ) ;
201201 case "CODE" : {
202202 // Escape backticks by using multiple backticks for the fence if necessary
203203 const fence = "`" . repeat ( longestBacktickSequence ( n . textContent ) + 1 ) ;
204204 return pc . plainWithEmoji ( `${ fence } ${ n . textContent } ${ fence } ` ) ;
205205 }
206206 case "BLOCKQUOTE" : {
207- const parts = parseChildren ( n , pc ) ;
207+ const parts = parseChildren ( n , pc , opts ) ;
208208 prefixLines ( parts , "> " , pc ) ;
209209 return parts ;
210210 }
211211 case "LI" :
212- return mkListItem ?.( n ) ?? parseChildren ( n , pc ) ;
212+ return mkListItem ?.( n ) ?? parseChildren ( n , pc , opts ) ;
213213 case "UL" : {
214- const parts = parseChildren ( n , pc , li => [ pc . plain ( "- " ) , ...parseChildren ( li , pc ) ] ) ;
214+ const parts = parseChildren ( n , pc , opts , li => [ pc . plain ( "- " ) , ...parseChildren ( li , pc , opts ) ] ) ;
215215 if ( isListChild ( n ) ) {
216216 prefixLines ( parts , " " , pc ) ;
217217 }
218218 return parts ;
219219 }
220220 case "OL" : {
221221 let counter = ( n as HTMLOListElement ) . start ?? 1 ;
222- const parts = parseChildren ( n , pc , li => {
223- const parts = [ pc . plain ( `${ counter } . ` ) , ...parseChildren ( li , pc ) ] ;
222+ const parts = parseChildren ( n , pc , opts , li => {
223+ const parts = [ pc . plain ( `${ counter } . ` ) , ...parseChildren ( li , pc , opts ) ] ;
224224 counter ++ ;
225225 return parts ;
226226 } ) ;
@@ -247,15 +247,20 @@ function parseNode(n: Node, pc: PartCreator, mkListItem?: (li: Node) => Part[]):
247247 }
248248 }
249249
250- return parseChildren ( n , pc ) ;
250+ return parseChildren ( n , pc , opts ) ;
251251}
252252
253- function parseHtmlMessage ( html : string , pc : PartCreator , isQuotedMessage : boolean ) : Part [ ] {
253+ interface IParseOptions {
254+ isQuotedMessage ?: boolean ;
255+ shouldEscape ?: boolean ;
256+ }
257+
258+ function parseHtmlMessage ( html : string , pc : PartCreator , opts : IParseOptions ) : Part [ ] {
254259 // no nodes from parsing here should be inserted in the document,
255260 // as scripts in event handlers, etc would be executed then.
256261 // we're only taking text, so that is fine
257- const parts = parseNode ( new DOMParser ( ) . parseFromString ( html , "text/html" ) . body , pc ) ;
258- if ( isQuotedMessage ) {
262+ const parts = parseNode ( new DOMParser ( ) . parseFromString ( html , "text/html" ) . body , pc , opts ) ;
263+ if ( opts . isQuotedMessage ) {
259264 prefixLines ( parts , "> " , pc ) ;
260265 }
261266 return parts ;
@@ -264,14 +269,14 @@ function parseHtmlMessage(html: string, pc: PartCreator, isQuotedMessage: boolea
264269export function parsePlainTextMessage (
265270 body : string ,
266271 pc : PartCreator ,
267- opts : { isQuotedMessage ?: boolean , shouldEscape ?: boolean } ,
272+ opts : IParseOptions ,
268273) : Part [ ] {
269274 const lines = body . split ( / \r \n | \r | \n / g) ; // split on any new-line combination not just \n, collapses \r\n
270275 return lines . reduce ( ( parts , line , i ) => {
271276 if ( opts . isQuotedMessage ) {
272277 parts . push ( pc . plain ( "> " ) ) ;
273278 }
274- parts . push ( ...parseAtRoomMentions ( line , pc , opts . shouldEscape ) ) ;
279+ parts . push ( ...parseAtRoomMentions ( line , pc , opts ) ) ;
275280 const isLast = i === lines . length - 1 ;
276281 if ( ! isLast ) {
277282 parts . push ( pc . newline ( ) ) ;
@@ -280,19 +285,19 @@ export function parsePlainTextMessage(
280285 } , [ ] as Part [ ] ) ;
281286}
282287
283- export function parseEvent ( event : MatrixEvent , pc : PartCreator , { isQuotedMessage = false } = { } ) {
288+ export function parseEvent ( event : MatrixEvent , pc : PartCreator , opts : IParseOptions = { shouldEscape : true } ) {
284289 const content = event . getContent ( ) ;
285290 let parts : Part [ ] ;
286291 const isEmote = content . msgtype === "m.emote" ;
287292 let isRainbow = false ;
288293
289294 if ( content . format === "org.matrix.custom.html" ) {
290- parts = parseHtmlMessage ( content . formatted_body || "" , pc , isQuotedMessage ) ;
295+ parts = parseHtmlMessage ( content . formatted_body || "" , pc , opts ) ;
291296 if ( content . body && content . formatted_body && textToHtmlRainbow ( content . body ) === content . formatted_body ) {
292297 isRainbow = true ;
293298 }
294299 } else {
295- parts = parsePlainTextMessage ( content . body || "" , pc , { isQuotedMessage } ) ;
300+ parts = parsePlainTextMessage ( content . body || "" , pc , opts ) ;
296301 }
297302
298303 if ( isEmote && isRainbow ) {
0 commit comments