@@ -81,40 +81,57 @@ function capitalize(str: string): string {
81
81
}
82
82
83
83
const MarkdownItAlerts = ( md : MarkdownIt ) => {
84
+ // Alert title line example:
85
+ // > [!marker] Optional title
86
+
84
87
// Allow multi word alphanumeric markers (includes underscore)
85
88
// Additionally allow dashes in marker
86
- const markerRE = '[\\w- ]+' ;
87
-
88
- // Match marker case insensitively
89
- // Note: config icons and titles keys must be fully lowercase
90
- const RE = new RegExp ( `^\\\\?\\[\\!(${ markerRE } )\\]([^\\n\\r]*)` , 'i' ) ;
89
+ // Match marker case-insensitively
90
+ const titlePattern = / ^ \[ ! ( [ \w \- ] + ) \] ( [ ^ \n \r ] * ) / i;
91
91
92
92
md . core . ruler . after ( 'block' , 'alerts' , ( state ) => {
93
93
const tokens = state . tokens ;
94
+
94
95
for ( let i = 0 ; i < tokens . length ; i ++ ) {
95
- if ( tokens [ i ] . type === 'blockquote_open' ) {
96
- const open = tokens [ i ] ;
97
- const startIndex = i ;
98
- while ( tokens [ i ] ?. type !== 'blockquote_close' && i <= tokens . length ) i += 1 ;
99
- const close = tokens [ i ] ;
100
- const endIndex = i ;
101
- const firstContent = tokens
102
- . slice ( startIndex , endIndex + 1 )
103
- . find ( ( token ) => token . type === 'inline' ) ;
104
- if ( ! firstContent ) continue ;
105
- const match = firstContent . content . match ( RE ) ;
106
- if ( ! match ) continue ;
107
- const marker = match [ 1 ] . toLowerCase ( ) ;
108
- const title = match [ 2 ] . trim ( ) || ( titles [ marker ] ?? capitalize ( marker ) ) ;
109
- const isFallback = ! ( marker in resolvedIcons ) ; // For styling unconfigured markers
110
- const icon = isFallback ? fallbackIcon : resolvedIcons [ marker ] ;
111
- firstContent . content = firstContent . content . slice ( match [ 0 ] . length ) . trimStart ( ) ;
112
- open . type = 'alert_open' ;
113
- open . tag = 'div' ;
114
- open . meta = { marker, title, icon, isFallback } ;
115
- close . type = 'alert_close' ;
116
- close . tag = 'div' ;
96
+ if ( tokens [ i ] . type !== 'blockquote_open' ) continue ;
97
+
98
+ const open = tokens [ i ] ;
99
+ const start = i ;
100
+
101
+ while ( i < tokens . length && tokens [ i ] . type !== 'blockquote_close' ) i ++ ;
102
+
103
+ const close = tokens [ i ] ;
104
+ const end = i ;
105
+
106
+ // Get the first inline token, consists of:
107
+ // 1. title line e.g. [!marker] Optional title
108
+ // 2. the first paragraph in the alert body
109
+ let firstBlock ;
110
+ for ( let j = start ; j <= end ; j ++ ) {
111
+ if ( tokens [ j ] . type === 'inline' ) {
112
+ firstBlock = tokens [ j ] ;
113
+ break ;
114
+ }
117
115
}
116
+ if ( ! firstBlock ) continue ;
117
+
118
+ // Is this blockquote an alert?
119
+ const match = firstBlock . content . match ( titlePattern ) ;
120
+ if ( ! match ) continue ;
121
+
122
+ const marker = match [ 1 ] . toLowerCase ( ) ;
123
+ const title = match [ 2 ] . trim ( ) || ( titles [ marker ] ?? capitalize ( marker ) ) ;
124
+ const isFallback = ! ( marker in resolvedIcons ) ; // For styling unconfigured markers
125
+ const icon = isFallback ? fallbackIcon : resolvedIcons [ marker ] ;
126
+
127
+ // Remove the title line, to be replaced by the final alert title
128
+ firstBlock . content = firstBlock . content . slice ( match [ 0 ] . length ) . trimStart ( ) ;
129
+
130
+ open . type = 'alert_open' ;
131
+ open . tag = 'div' ;
132
+ open . meta = { marker, title, icon, isFallback } ;
133
+ close . type = 'alert_close' ;
134
+ close . tag = 'div' ;
118
135
}
119
136
} ) ;
120
137
md . renderer . rules . alert_open = function ( tokens , idx ) {
0 commit comments