@@ -66,13 +66,16 @@ function addAttributeInElement(node, elementTarget, separator) {
6666 const attrsInNode = new RegExp ( separator , "gm" ) ;
6767 const attrsRegex = new RegExp (
6868 // attributes are limited to prevent code injection
69- " (?:^|\s)(?<attr>class|style|data-[a-z-]+)=(?:\ "(?<dval>[^\ "]+?)\ "|'(?<sval>[^']+?)')" ,
69+ / (?: ^ | \s + ) (?< attr > c l a s s | s t y l e | d a t a - [ a - z - ] + ) = (?: " (?< dval > [ ^ " ] + ?) " | ' (?< sval > [ ^ ' ] + ?) ' ) / ,
7070 "gm" ,
7171 ) ;
7272 let matches ,
7373 matchesAttrs ;
7474 if ( ( matches = attrsInNode . exec ( node . nodeValue ) ) !== null ) {
7575 const attrs = matches [ 1 ] ;
76+ // remove match from comment node to avoid further processing
77+ node . nodeValue = node . nodeValue . substring ( 0 , matches . index ) +
78+ node . nodeValue . substring ( attrsInNode . lastIndex ) ;
7679 while ( ( matchesAttrs = attrsRegex . exec ( attrs ) ) !== null ) {
7780 elementTarget . setAttribute (
7881 matchesAttrs . groups . attr ,
@@ -134,14 +137,19 @@ function addAttributes(
134137 }
135138 }
136139 if ( element . nodeType == Node . COMMENT_NODE ) {
140+ let attrs_added = false ;
141+ // Avoid adding attributes to the section if separatorElementAttributes is used
137142 if ( previousElement !== section ) {
138- addAttributeInElement (
143+ attrs_added = addAttributeInElement (
139144 element ,
140145 previousElement ,
141146 separatorElementAttributes ,
142147 ) ;
143148 }
144- addAttributeInElement ( element , section , separatorSectionAttributes ) ;
149+ // Speed optimization: only add attributes if they haven't been added before
150+ if ( ! attrs_added ) {
151+ addAttributeInElement ( element , section , separatorSectionAttributes ) ;
152+ }
145153 }
146154}
147155
@@ -159,6 +167,69 @@ function addSlidifyDefaultOptions(options) {
159167 return options ;
160168}
161169
170+ const IS_URL = / ^ h t t p s ? : \/ \/ / ;
171+ const IS_ABSOLUTE = / ^ \/ / ;
172+ const IS_LOCAL = / ^ # / ;
173+ const IS_RELATIVE = / ^ ( \. \. \/ | \. \/ ) / ;
174+
175+ /**
176+ * Takes an HTML marked token and converts relative URLs in img, a and comment elements into absolute URLs.
177+ */
178+ function rebasePathIfNeeded ( base_url , token ) {
179+ let text = [ ] ;
180+ let last_index = 0 ;
181+ let remainder = "" ;
182+ const a_href_regex =
183+ / ( ( < a [ ^ > ] * h r e f = " ) ( [ ^ " ] * ) ( " [ ^ > ] * > ) | ( < a [ ^ > ] * h r e f = ' ) ( [ ^ ' ] * ) ( ' [ ^ > ] * > ) ) / gi;
184+ const img_src_regex =
185+ / ( ( < i m g [ ^ > ] * s r c = " ) ( [ ^ " ] * ) ( " [ ^ > ] * > ) | ( < i m g [ ^ > ] * s r c = ' ) ( [ ^ ' ] * ) ( ' [ ^ > ] * > ) ) / gi;
186+ const img_data_preview_image_regex =
187+ / ( ( < i m g [ ^ > ] * d a t a - p r e v i e w - i m a g e = " ) ( [ ^ " ] * ) ( " [ ^ > ] * > ) | ( < i m g [ ^ > ] * d a t a - p r e v i e w - i m a g e = ' ) ( [ ^ ' ] * ) ( ' [ ^ > ] * > ) ) / gi;
188+ const data_background_image_regex =
189+ / ( ( \s d a t a - b a c k g r o u n d - i m a g e = " ) ( [ ^ " ] * ) ( " ) | ( \s d a t a - b a c k g r o u n d - i m a g e = ' ) ( [ ^ ' ] * ) ( ' ) ) / gi;
190+ // data-background-image
191+ for (
192+ const regex of [
193+ a_href_regex ,
194+ img_src_regex ,
195+ img_data_preview_image_regex ,
196+ data_background_image_regex ,
197+ ]
198+ ) {
199+ for ( const match of token . text . matchAll ( regex ) ) {
200+ // offset to select between single or double qoutes path in regex
201+ const matchOffset = match [ 2 ] ? 0 : 3 ;
202+ text . push ( token . text . substring ( last_index , match . index ) ) ;
203+ const ref = match [ 3 + matchOffset ] ;
204+ const needsRebase =
205+ ! ( IS_URL . test ( ref ) || IS_ABSOLUTE . test ( ref ) || IS_LOCAL . test ( ref ) ) ;
206+ // const needsRebase = isRelative.test(ref);
207+ let rebasing = "" ;
208+ let path_url = "" ;
209+ if ( needsRebase ) {
210+ rebasing = base_url ;
211+ }
212+ if ( needsRebase ) {
213+ // Normalize URL if it needs rebasing
214+ path_url = new URL ( `${ rebasing } ${ match [ 3 + matchOffset ] } ` ) . toString ( ) ;
215+ } else {
216+ path_url = `${ rebasing } ${ match [ 3 + matchOffset ] } ` ;
217+ }
218+ text . push (
219+ `${ match [ 2 + matchOffset ] } ${ path_url } ${ match [ 4 + matchOffset ] } ` ,
220+ ) ;
221+ last_index = match . index + match [ 0 ] . length ;
222+ }
223+ if ( text . length ) {
224+ remainder = token . text . substring ( last_index , token . text . length ) ;
225+ token . text = text . join ( "" ) + remainder ;
226+ }
227+ text = [ ] ;
228+ last_index = 0 ;
229+ }
230+ return { text, last_index, remainder } ;
231+ }
232+
162233export function buildMarkedConfiguration ( markedOptions ) {
163234 // Marked options: https://marked.js.org/using_advanced#options
164235 // baseUrl for html elements a and img
@@ -174,15 +245,6 @@ export function buildMarkedConfiguration(markedOptions) {
174245 marked . use ( gfmHeadingId ( ) ) ;
175246 markedOptions . async = true ;
176247 markedOptions . useNewRenderer = true ;
177- const a_href_regex =
178- / ( ( < a [ ^ > ] * ? h r e f = " ) ( [ ^ " ] * ?) ( " [ ^ > ] * ?> ) | ( < a [ ^ > ] * ? h r e f = ' ) ( [ ^ ' ] + ?) ( ' [ ^ > ] * ?> ) ) / gi;
179- // TODO: apply img src also to data-preview-image
180- const img_src_regex =
181- / ( ( < i m g [ ^ > ] * ? s r c = " ) ( [ ^ " ] * ?) ( " [ ^ > ] * ?> ) | ( < i m g [ ^ > ] * ? s r c = ' ) ( [ ^ ' ] + ?) ( ' [ ^ > ] * ?> ) ) / gi;
182- const isUrl = / ^ h t t p s ? : \/ \/ / ;
183- const isAbsolute = / ^ \/ / ;
184- const isLocal = / ^ # / ;
185- const isRelative = / ^ ( \. \. \/ | \. \/ ) / ;
186248 const markedConfig = {
187249 ...markedOptions ,
188250 renderer : {
@@ -192,58 +254,10 @@ export function buildMarkedConfiguration(markedOptions) {
192254 } ,
193255 walkTokens : ( token ) => {
194256 if ( token . type === "html" ) {
195- let text = [ ] ;
196- let last_index = 0 ;
197- let remainder = "" ;
198- for ( const match of token . text . matchAll ( img_src_regex ) ) {
199- const matchOffset = match [ 2 ] ? 0 : 3 ;
200- text . push ( token . text . substring ( last_index , match . index ) ) ;
201- const ref = match [ 3 + matchOffset ] ;
202- const needsRebase =
203- ! ( isUrl . test ( ref ) || isAbsolute . test ( ref ) || isLocal . test ( ref ) ) ;
204- // const needsRebase = isRelative.test(ref);
205- if ( needsRebase ) {
206- text . push (
207- `${ match [ 2 + matchOffset ] } ${ base_url } ${ match [ 3 + matchOffset ] } ${
208- match [ 4 + matchOffset ]
209- } `,
210- ) ;
211- } else {
212- text . push (
213- `${ match [ 2 + matchOffset ] } ${ match [ 3 + matchOffset ] } ${
214- match [ 4 + matchOffset ]
215- } `,
216- ) ;
217- }
218- last_index = match . index + match [ 0 ] . length ;
219- }
220- if ( text . length ) {
221- remainder = token . text . substring ( last_index , token . text . length ) ;
222- token . text = text . join ( "" ) + remainder ;
223- }
224- text = [ ] ;
225- last_index = 0 ;
226- for ( const match of token . text . matchAll ( a_href_regex ) ) {
227- text . push ( token . text . substring ( last_index , match . index ) ) ;
228- const matchOffset = match [ 2 ] ? 0 : 3 ;
229- const ref = match [ 3 + matchOffset ] ;
230- // const needsRebase = !(isUrl.test(ref) || isAbsolute.test(ref) || isLocal.test(ref))
231- const needsRebase = isRelative . test ( ref ) ;
232- if ( needsRebase ) {
233- text . push (
234- `${ match [ 2 + matchOffset ] } ${ base_url } ${ match [ 3 + matchOffset ] } ${
235- match [ 4 + matchOffset ]
236- } `,
237- ) ;
238- } else {
239- text . push (
240- `${ match [ 2 + matchOffset ] } ${ match [ 3 + matchOffset ] } ${
241- match [ 4 + matchOffset ]
242- } `,
243- ) ;
244- }
245- last_index = match . index + match [ 0 ] . length ;
246- }
257+ let { text, last_index, remainder } = rebasePathIfNeeded (
258+ base_url ,
259+ token ,
260+ ) ;
247261 if ( text . length ) {
248262 remainder = token . text . substring ( last_index , token . text . length ) ;
249263 token . text = text . join ( "" ) + remainder ;
0 commit comments