renderer.js 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. const zlib = require('zlib')
  2. // ------------------------------------
  3. // Markdown - Kroki Preprocessor
  4. // ------------------------------------
  5. module.exports = {
  6. init (mdinst, conf) {
  7. mdinst.use((md, opts) => {
  8. const openMarker = opts.openMarker || '```kroki'
  9. const openChar = openMarker.charCodeAt(0)
  10. const closeMarker = opts.closeMarker || '```'
  11. const closeChar = closeMarker.charCodeAt(0)
  12. const server = opts.server || 'https://kroki.io'
  13. md.block.ruler.before('fence', 'kroki', (state, startLine, endLine, silent) => {
  14. let nextLine
  15. let markup
  16. let params
  17. let token
  18. let i
  19. let autoClosed = false
  20. let start = state.bMarks[startLine] + state.tShift[startLine]
  21. let max = state.eMarks[startLine]
  22. // Check out the first character quickly,
  23. // this should filter out most of non-uml blocks
  24. //
  25. if (openChar !== state.src.charCodeAt(start)) { return false }
  26. // Check out the rest of the marker string
  27. //
  28. for (i = 0; i < openMarker.length; ++i) {
  29. if (openMarker[i] !== state.src[start + i]) { return false }
  30. }
  31. markup = state.src.slice(start, start + i)
  32. params = state.src.slice(start + i, max)
  33. // Since start is found, we can report success here in validation mode
  34. //
  35. if (silent) { return true }
  36. // Search for the end of the block
  37. //
  38. nextLine = startLine
  39. for (;;) {
  40. nextLine++
  41. if (nextLine >= endLine) {
  42. // unclosed block should be autoclosed by end of document.
  43. // also block seems to be autoclosed by end of parent
  44. break
  45. }
  46. start = state.bMarks[nextLine] + state.tShift[nextLine]
  47. max = state.eMarks[nextLine]
  48. if (start < max && state.sCount[nextLine] < state.blkIndent) {
  49. // non-empty line with negative indent should stop the list:
  50. // - ```
  51. // test
  52. break
  53. }
  54. if (closeChar !== state.src.charCodeAt(start)) {
  55. // didn't find the closing fence
  56. continue
  57. }
  58. if (state.sCount[nextLine] > state.sCount[startLine]) {
  59. // closing fence should not be indented with respect of opening fence
  60. continue
  61. }
  62. let closeMarkerMatched = true
  63. for (i = 0; i < closeMarker.length; ++i) {
  64. if (closeMarker[i] !== state.src[start + i]) {
  65. closeMarkerMatched = false
  66. break
  67. }
  68. }
  69. if (!closeMarkerMatched) {
  70. continue
  71. }
  72. // make sure tail has spaces only
  73. if (state.skipSpaces(start + i) < max) {
  74. continue
  75. }
  76. // found!
  77. autoClosed = true
  78. break
  79. }
  80. let contents = state.src
  81. .split('\n')
  82. .slice(startLine + 1, nextLine)
  83. .join('\n')
  84. // We generate a token list for the alt property, to mimic what the image parser does.
  85. let altToken = []
  86. // Remove leading space if any.
  87. let alt = params ? params.slice(1) : 'uml diagram'
  88. state.md.inline.parse(
  89. alt,
  90. state.md,
  91. state.env,
  92. altToken
  93. )
  94. let firstlf = contents.indexOf('\n')
  95. if (firstlf === -1) firstlf = undefined
  96. let diagramType = contents.substring(0, firstlf)
  97. contents = contents.substring(firstlf + 1)
  98. let result = zlib.deflateSync(contents).toString('base64').replace(/\+/g, '-').replace(/\//g, '_')
  99. token = state.push('kroki', 'img', 0)
  100. // alt is constructed from children. No point in populating it here.
  101. token.attrs = [ [ 'src', `${server}/${diagramType}/svg/${result}` ], [ 'alt', '' ], ['class', 'uml-diagram prefetch-candidate'] ]
  102. token.block = true
  103. token.children = altToken
  104. token.info = params
  105. token.map = [ startLine, nextLine ]
  106. token.markup = markup
  107. state.line = nextLine + (autoClosed ? 1 : 0)
  108. return true
  109. }, {
  110. alt: [ 'paragraph', 'reference', 'blockquote', 'list' ]
  111. })
  112. md.renderer.rules.kroki = md.renderer.rules.image
  113. }, {
  114. openMarker: conf.openMarker,
  115. closeMarker: conf.closeMarker,
  116. server: conf.server
  117. })
  118. }
  119. }