@@ -23,10 +23,13 @@ let parser;
2323const Markdown = {
2424 config : { } ,
2525 _externalImageCache : undefined ,
26+ _externalImageFailures : new Set ( ) ,
2627 onLoad : async function ( params ) {
2728 const controllers = require ( './lib/controllers' ) ;
2829 const hostMiddleware = require . main . require ( './src/middleware' ) ;
29- const middlewares = [ hostMiddleware . maintenanceMode , hostMiddleware . registrationComplete , hostMiddleware . pluginHooks ] ;
30+ const middlewares = [
31+ hostMiddleware . maintenanceMode , hostMiddleware . registrationComplete , hostMiddleware . pluginHooks ,
32+ ] ;
3033
3134 params . router . get ( '/admin/plugins/markdown' , params . middleware . admin . buildHeader , controllers . renderAdmin ) ;
3235 params . router . get ( '/api/admin/plugins/markdown' , controllers . renderAdmin ) ;
@@ -35,35 +38,36 @@ const Markdown = {
3538 params . router . get ( '/api/post/:pid/raw' , middlewares , controllers . retrieveRaw ) ;
3639
3740 Markdown . init ( ) ;
38- Markdown . loadThemes ( ) ;
41+ await Markdown . loadThemes ( ) ;
3942
4043 return params ;
4144 } ,
4245
43- getConfig : function ( config ) {
46+ getConfig : async ( config ) => {
47+ const { defaultHighlightLanguage, highlightTheme } = await meta . settings . get ( 'markdown' ) ;
48+
4449 config . markdown = {
4550 highlight : Markdown . highlight ? 1 : 0 ,
4651 highlightLinesLanguageList : Markdown . config . highlightLinesLanguageList ,
47- theme : Markdown . config . highlightTheme || 'railscasts.css' ,
52+ theme : highlightTheme || 'default.css' ,
53+ defaultHighlightLanguage : defaultHighlightLanguage || '' ,
4854 } ;
55+
4956 return config ;
5057 } ,
5158
52- getLinkTags : function ( hookData ) {
59+ getLinkTags : async ( hookData ) => {
60+ const { highlightTheme } = await meta . settings . get ( 'markdown' ) ;
61+
5362 hookData . links . push ( {
5463 rel : 'prefetch stylesheet' ,
5564 type : '' ,
56- href : `${ nconf . get ( 'relative_path' ) } /plugins/nodebb-plugin-markdown/styles/${ Markdown . config . highlightTheme || 'railscasts.css' } ` ,
65+ href : `${ nconf . get ( 'relative_path' ) } /assets/plugins/nodebb-plugin-markdown/styles/${ highlightTheme || 'default.css' } ` ,
66+ } , {
67+ rel : 'prefetch' ,
68+ href : `${ nconf . get ( 'relative_path' ) } /assets/language/${ meta . config . defaultLang || 'en-GB' } /markdown.json?${ meta . config [ 'cache-buster' ] } ` ,
5769 } ) ;
5870
59- const prefetch = [ '/assets/src/modules/highlight.js' , `/assets/language/${ meta . config . defaultLang || 'en-GB' } /markdown.json` ] ;
60- hookData . links = hookData . links . concat (
61- prefetch . map ( ( path ) => ( {
62- rel : 'prefetch' ,
63- href : nconf . get ( 'relative_path' ) + path + '?' + meta . config [ 'cache-buster' ] ,
64- } ) )
65- ) ;
66-
6771 return hookData ;
6872 } ,
6973
@@ -76,7 +80,7 @@ const Markdown = {
7680 langPrefix : 'language-' ,
7781 highlight : true ,
7882 highlightLinesLanguageList : [ ] ,
79- highlightTheme : 'railscasts .css' ,
83+ highlightTheme : 'default .css' ,
8084
8185 probe : true ,
8286 probeCacheSize : 256 ,
@@ -93,12 +97,12 @@ const Markdown = {
9397 } ;
9498 const notCheckboxes = [ 'langPrefix' , 'highlightTheme' , 'highlightLinesLanguageList' , 'probeCacheSize' ] ;
9599
96- meta . settings . get ( 'markdown' , function ( err , options ) {
100+ meta . settings . get ( 'markdown' , ( err , options ) => {
97101 if ( err ) {
98102 winston . warn ( `[plugin/markdown] Unable to retrieve settings, assuming defaults: ${ err . message } ` ) ;
99103 }
100104
101- for ( const field in defaults ) {
105+ Object . keys ( defaults ) . forEach ( ( field ) => {
102106 // If not set in config (nil)
103107 if ( ! options . hasOwnProperty ( field ) ) {
104108 _self . config [ field ] = defaults [ field ] ;
@@ -107,7 +111,7 @@ const Markdown = {
107111 } else {
108112 _self . config [ field ] = options [ field ] ;
109113 }
110- }
114+ } ) ;
111115
112116 _self . highlight = _self . config . highlight ;
113117 delete _self . config . highlight ;
@@ -133,28 +137,21 @@ const Markdown = {
133137 name : 'markdown.externalImageCache' ,
134138 max : parseInt ( _self . config . probeCacheSize , 10 ) || 256 ,
135139 length : function ( ) { return 1 ; } ,
136- maxAge : 1000 * 60 * 60 * 24 , // 1 day
140+ maxAge : 1000 * 60 * 60 * 24 , // 1 day
137141 } ) ;
138142 }
139143 } ) ;
140144 } ,
141145
142- loadThemes : function ( ) {
143- fs . readdir ( path . join ( require . resolve ( 'highlight.js' ) , '../../styles' ) , function ( err , files ) {
144- if ( err ) {
145- winston . error ( '[plugin/markdown] Could not load Markdown themes: ' + err . message ) ;
146- Markdown . themes = [ ] ;
147- return ;
148- }
146+ loadThemes : async ( ) => {
147+ try {
148+ const files = await fs . promises . readdir ( path . join ( require . resolve ( 'highlight.js' ) , '../../styles' ) ) ;
149149 const isStylesheet = / \. c s s $ / ;
150- Markdown . themes = files . filter ( function ( file ) {
151- return isStylesheet . test ( file ) ;
152- } ) . map ( function ( file ) {
153- return {
154- name : file ,
155- } ;
156- } ) ;
157- } ) ;
150+ Markdown . themes = files . filter ( file => isStylesheet . test ( file ) ) ;
151+ } catch ( err ) {
152+ winston . error ( `[plugin/markdown] Could not load Markdown themes: ${ err . message } ` ) ;
153+ Markdown . themes = [ ] ;
154+ }
158155 } ,
159156 parsePost : async function ( data ) {
160157 const env = await Markdown . beforeParse ( data ) ;
@@ -208,29 +205,36 @@ const Markdown = {
208205 // eslint-disable-next-line no-cond-assign
209206 while ( ( current = matcher . exec ( data . postData . content ) ) !== null ) {
210207 const match = current [ 1 ] ;
211- if ( match && Markdown . isExternalLink ( match ) ) { // for security only parse external images
208+ if ( match && Markdown . isExternalLink ( match ) ) { // for security only parse external images
212209 const parsedUrl = url . parse ( match ) ;
213210 const filename = path . basename ( parsedUrl . pathname ) ;
214211 const size = Markdown . _externalImageCache . get ( match ) ;
212+
213+ // Short-circuit to ignore previous failures
214+ const hasFailed = Markdown . _externalImageFailures . has ( match ) ;
215+ if ( hasFailed ) {
216+ return ;
217+ }
218+
215219 if ( size ) {
216220 env . images . set ( filename , size ) ;
217221 } else {
218- try {
219- // eslint-disable-next-line no-await-in-loop
220- const size = await probe ( match ) ;
221-
222+ // Size checked asynchronously, see: https://github.com/tomas/needle/issues/389
223+ probe ( match , {
224+ follow_max : 2 ,
225+ } ) . then ( ( size ) => {
222226 let { width, height } = size ;
223227
224228 // Swap width and height if orientation bit is set
225229 if ( size . orientation >= 5 && size . orientation <= 8 ) {
226230 [ width , height ] = [ height , width ] ;
227231 }
228232
229- env . images . set ( filename , { width, height } ) ;
230233 Markdown . _externalImageCache . set ( match , { width, height } ) ;
231- } catch ( e ) {
232- // No handling required
233- }
234+ } ) . catch ( ( ) => {
235+ // Likely an issue getting the external image size, ignore in the future
236+ Markdown . _externalImageFailures . add ( match ) ;
237+ } ) ;
234238 }
235239 }
236240 }
@@ -248,13 +252,9 @@ const Markdown = {
248252 const execute = function ( html ) {
249253 // Replace all italicised mentions back to regular mentions
250254 if ( italicMention . test ( html ) ) {
251- html = html . replace ( italicMention , function ( match , slug ) {
252- return '@_' + slug + '_' ;
253- } ) ;
255+ html = html . replace ( italicMention , ( match , slug ) => `@_${ slug } _` ) ;
254256 } else if ( boldMention . test ( html ) ) {
255- html = html . replace ( boldMention , function ( match , slug ) {
256- return '@__' + slug + '__' ;
257- } ) ;
257+ html = html . replace ( boldMention , ( match , slug ) => `@__${ slug } __` ) ;
258258 }
259259
260260 return html ;
@@ -342,12 +342,15 @@ const Markdown = {
342342
343343 // Update renderer to add some classes to all images
344344 const renderImage = parser . renderer . rules . image || function ( tokens , idx , options , env , self ) {
345+ // eslint-disable-next-line prefer-spread,prefer-rest-params
345346 return self . renderToken . apply ( self , arguments ) ;
346347 } ;
347348 const renderLink = parser . renderer . rules . link_open || function ( tokens , idx , options , env , self ) {
349+ // eslint-disable-next-line prefer-spread,prefer-rest-params
348350 return self . renderToken . apply ( self , arguments ) ;
349351 } ;
350352 const renderTable = parser . renderer . rules . table_open || function ( tokens , idx , options , env , self ) {
353+ // eslint-disable-next-line prefer-spread,prefer-rest-params
351354 return self . renderToken . apply ( self , arguments ) ;
352355 } ;
353356
@@ -359,7 +362,7 @@ const Markdown = {
359362 // Validate the url
360363 if ( ! Markdown . isUrlValid ( attributes . get ( 'src' ) ) ) { return '' ; }
361364
362- token . attrSet ( 'class' , ( token . attrGet ( 'class' ) || '' ) + ' img-responsive img-markdown' ) ;
365+ token . attrSet ( 'class' , ` ${ token . attrGet ( 'class' ) || '' } img-responsive img-markdown` ) ;
363366
364367 // Append sizes to images
365368 if ( parsedSrc . pathname ) {
@@ -440,7 +443,7 @@ const Markdown = {
440443 * get updated.
441444 */
442445 const allowedRoots = [ nconf . get ( 'upload_url' ) , '/uploads' ] ;
443- const allowed = ( pathname ) => allowedRoots . some ( ( root ) => pathname . toString ( ) . startsWith ( root ) || pathname . toString ( ) . startsWith ( nconf . get ( 'relative_path' ) + root ) ) ;
446+ const allowed = pathname => allowedRoots . some ( root => pathname . toString ( ) . startsWith ( root ) || pathname . toString ( ) . startsWith ( nconf . get ( 'relative_path' ) + root ) ) ;
444447
445448 try {
446449 const urlObj = url . parse ( src , false , true ) ;
@@ -461,9 +464,12 @@ const Markdown = {
461464 }
462465
463466 if (
464- urlObj . host === null // Relative paths are always internal links...
465- || ( urlObj . host === baseUrlObj . host && urlObj . protocol === baseUrlObj . protocol // Otherwise need to check that protocol and host match
466- && ( nconf . get ( 'relative_path' ) . length > 0 ? urlObj . pathname . indexOf ( nconf . get ( 'relative_path' ) ) === 0 : true ) ) // Subfolder installs need this additional check
467+ urlObj . host === null || // Relative paths are always internal links...
468+ (
469+ urlObj . host === baseUrlObj . host &&
470+ urlObj . protocol === baseUrlObj . protocol && // Otherwise need to check that protocol and host match
471+ ( nconf . get ( 'relative_path' ) . length > 0 ? urlObj . pathname . indexOf ( nconf . get ( 'relative_path' ) ) === 0 : true ) // Subfolder installs need this additional check
472+ )
467473 ) {
468474 return false ;
469475 }
0 commit comments