@@ -9,6 +9,7 @@ import fs from "fs-extra";
9
9
import mime from "mime-types" ;
10
10
import path from "path" ;
11
11
import sharp from "sharp" ;
12
+ import { nanoid } from "nanoid" ;
12
13
13
14
const convertAsync = promisify ( convert ) ;
14
15
@@ -311,3 +312,133 @@ export const convertKeysToSnakeCase = (
311
312
Object . entries ( obj ) . map ( ( [ key , value ] ) => [ camelToSnakeCase ( key ) , value ] )
312
313
) ;
313
314
} ;
315
+
316
+ interface ProcessedNode {
317
+ id : string ;
318
+ parentId : string | undefined ;
319
+ type : string ;
320
+ value : any ;
321
+ }
322
+ interface parentId {
323
+ id : string ;
324
+ depth : number ;
325
+ }
326
+
327
+ export const markdownToJson = async ( markdownString : string ) => {
328
+ /**
329
+ * Bypassing typescript transpiler using eval to use dynamic imports
330
+ *
331
+ * Source: https://stackoverflow.com/a/70546326
332
+ */
333
+ const { unified } = await eval ( `import('unified')` ) ;
334
+ const { default : remarkParse } = await eval ( `import('remark-parse')` ) ;
335
+ const { remarkGfm } = await eval ( `import('remark-gfm')` ) ;
336
+
337
+ const parsedMd = unified ( )
338
+ . use ( remarkParse ) // Parse Markdown to AST
339
+ . use ( remarkGfm )
340
+ . parse ( markdownString ) ;
341
+
342
+ console . log ( JSON . stringify ( parsedMd ) ) ;
343
+
344
+ const parentIdManager : parentId [ ] = [ ] ;
345
+
346
+ const jsonObj : ProcessedNode [ ] = [ ] ;
347
+ parsedMd . children . forEach ( ( node : any ) => {
348
+ const isHeading = node . type === "heading" ;
349
+
350
+ if ( isHeading && node . depth <= ( parentIdManager . at ( - 1 ) ?. depth || 0 ) ) {
351
+ for ( let i = parentIdManager . length ; i > 0 ; i -- ) {
352
+ parentIdManager . pop ( ) ;
353
+ if ( node . depth > ( parentIdManager . at ( - 1 ) ?. depth || 0 ) ) {
354
+ break ;
355
+ }
356
+ }
357
+ }
358
+ const processedNode = processNode ( node , parentIdManager . at ( - 1 ) ?. id ) ;
359
+
360
+ if ( isHeading ) {
361
+ parentIdManager . push ( { id : processedNode [ 0 ] . id , depth : node . depth } ) ;
362
+ }
363
+
364
+ jsonObj . push ( ...processedNode ) ;
365
+ } ) ;
366
+
367
+ return jsonObj ;
368
+ } ;
369
+
370
+ const type : Record < string , string > = {
371
+ heading : "heading" ,
372
+ text : "text" ,
373
+ list : "list" ,
374
+ } ;
375
+
376
+ const processNode = ( node : any , parentId ?: string ) : ProcessedNode [ ] => {
377
+ let value : any ;
378
+ let siblingNodes : ProcessedNode [ ] = [ ] ;
379
+
380
+ if ( node . type === "heading" ) {
381
+ value = node . children
382
+ . map ( ( childNode : any ) => processText ( childNode ) )
383
+ . join ( " " ) ;
384
+ } else if ( node . type === "paragraph" ) {
385
+ value = node . children
386
+ . map ( ( childNode : any ) => processText ( childNode ) )
387
+ . join ( " " ) ;
388
+ } else if ( node . type === "list" ) {
389
+ const processedNodes = node . children . map ( ( childNode : any ) =>
390
+ processListItem ( childNode )
391
+ ) ;
392
+ value = [ ] ;
393
+ processedNodes . forEach ( ( pn : any ) => {
394
+ value . push ( ...pn . node ) ;
395
+ siblingNodes . push ( ...pn . siblings ) ;
396
+ } ) ;
397
+ }
398
+
399
+ return [
400
+ {
401
+ id : nanoid ( ) ,
402
+ parentId,
403
+ type : type [ node . type as string ] || type . text ,
404
+ value,
405
+ } ,
406
+ ...( siblingNodes || [ ] ) ,
407
+ ] ;
408
+ } ;
409
+
410
+ const processText = ( node : any ) => {
411
+ return node . value ;
412
+ } ;
413
+
414
+ const processListItem = ( node : any ) => {
415
+ let newNode : ProcessedNode [ ] = [ ] ;
416
+ let siblings : ProcessedNode [ ] = [ ] ;
417
+
418
+ node . children . forEach ( ( childNode : any ) => {
419
+ if ( childNode . type !== "list" ) {
420
+ const processedNode = processNode ( childNode ) ;
421
+ if ( newNode . length > 0 ) {
422
+ newNode [ 0 ] . value += processedNode . map ( ( { value } ) => value ) . join ( ", " ) ;
423
+ } else {
424
+ newNode [ 0 ] = processedNode [ 0 ] ;
425
+ }
426
+ siblings . push ( ...processedNode . slice ( 1 ) ) ;
427
+ } else {
428
+ if ( newNode . length == 0 ) {
429
+ newNode = [
430
+ {
431
+ id : nanoid ( ) ,
432
+ type : "text" ,
433
+ value : "" ,
434
+ parentId : undefined ,
435
+ } ,
436
+ ] ;
437
+ }
438
+ const processedNode = processNode ( childNode , newNode [ 0 ] . id ) ;
439
+ siblings . push ( ...processedNode ) ;
440
+ }
441
+ } ) ;
442
+
443
+ return { node : newNode , siblings } ;
444
+ } ;
0 commit comments