block.js 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. const util = require('./util')
  2. module.exports = mathBlock
  3. const lineFeed = 10 // '\n'
  4. const space = 32 // ' '
  5. const dollarSign = 36 // '$'
  6. const lineFeedChar = '\n'
  7. const dollarSignChar = '$'
  8. const minFenceCount = 2
  9. const classList = ['math', 'math-display']
  10. function mathBlock() {
  11. const parser = this.Parser
  12. const compiler = this.Compiler
  13. if (util.isRemarkParser(parser)) {
  14. attachParser(parser)
  15. }
  16. if (util.isRemarkCompiler(compiler)) {
  17. attachCompiler(compiler)
  18. }
  19. }
  20. function attachParser(parser) {
  21. const proto = parser.prototype
  22. const blockMethods = proto.blockMethods
  23. const interruptParagraph = proto.interruptParagraph
  24. const interruptList = proto.interruptList
  25. const interruptBlockquote = proto.interruptBlockquote
  26. proto.blockTokenizers.math = mathBlockTokenizer
  27. blockMethods.splice(blockMethods.indexOf('fencedCode') + 1, 0, 'math')
  28. // Inject math to interrupt rules
  29. interruptParagraph.splice(interruptParagraph.indexOf('fencedCode') + 1, 0, [
  30. 'math'
  31. ])
  32. interruptList.splice(interruptList.indexOf('fencedCode') + 1, 0, ['math'])
  33. interruptBlockquote.splice(interruptBlockquote.indexOf('fencedCode') + 1, 0, [
  34. 'math'
  35. ])
  36. function mathBlockTokenizer(eat, value, silent) {
  37. var length = value.length
  38. var index = 0
  39. let code
  40. let content
  41. let lineEnd
  42. let lineIndex
  43. let openingFenceIndentSize
  44. let openingFenceSize
  45. let openingFenceContentStart
  46. let closingFence
  47. let closingFenceSize
  48. let lineContentStart
  49. let lineContentEnd
  50. // Skip initial spacing.
  51. while (index < length && value.charCodeAt(index) === space) {
  52. index++
  53. }
  54. openingFenceIndentSize = index
  55. // Skip the fence.
  56. while (index < length && value.charCodeAt(index) === dollarSign) {
  57. index++
  58. }
  59. openingFenceSize = index - openingFenceIndentSize
  60. // Exit if there is not enough of a fence.
  61. if (openingFenceSize < minFenceCount) {
  62. return
  63. }
  64. // Skip spacing after the fence.
  65. while (index < length && value.charCodeAt(index) === space) {
  66. index++
  67. }
  68. openingFenceContentStart = index
  69. // Eat everything after the fence.
  70. while (index < length) {
  71. code = value.charCodeAt(index)
  72. // We don’t allow dollar signs here, as that could interfere with inline
  73. // math.
  74. if (code === dollarSign) {
  75. return
  76. }
  77. if (code === lineFeed) {
  78. break
  79. }
  80. index++
  81. }
  82. if (value.charCodeAt(index) !== lineFeed) {
  83. return
  84. }
  85. if (silent) {
  86. return true
  87. }
  88. content = []
  89. if (openingFenceContentStart !== index) {
  90. content.push(value.slice(openingFenceContentStart, index))
  91. }
  92. index++
  93. lineEnd = value.indexOf(lineFeedChar, index + 1)
  94. lineEnd = lineEnd === -1 ? length : lineEnd
  95. while (index < length) {
  96. closingFence = false
  97. lineContentStart = index
  98. lineContentEnd = lineEnd
  99. lineIndex = lineEnd
  100. closingFenceSize = 0
  101. // First, let’s see if this is a valid closing fence.
  102. // Skip trailing white space
  103. while (
  104. lineIndex > lineContentStart &&
  105. value.charCodeAt(lineIndex - 1) === space
  106. ) {
  107. lineIndex--
  108. }
  109. // Skip the fence.
  110. while (
  111. lineIndex > lineContentStart &&
  112. value.charCodeAt(lineIndex - 1) === dollarSign
  113. ) {
  114. closingFenceSize++
  115. lineIndex--
  116. }
  117. // Check if this is a valid closing fence line.
  118. if (
  119. openingFenceSize <= closingFenceSize &&
  120. value.indexOf(dollarSignChar, lineContentStart) === lineIndex
  121. ) {
  122. closingFence = true
  123. lineContentEnd = lineIndex
  124. }
  125. // Sweet, next, we need to trim the line.
  126. // Skip initial spacing.
  127. while (
  128. lineContentStart <= lineContentEnd &&
  129. lineContentStart - index < openingFenceIndentSize &&
  130. value.charCodeAt(lineContentStart) === space
  131. ) {
  132. lineContentStart++
  133. }
  134. // If this is a closing fence, skip final spacing.
  135. if (closingFence) {
  136. while (
  137. lineContentEnd > lineContentStart &&
  138. value.charCodeAt(lineContentEnd - 1) === space
  139. ) {
  140. lineContentEnd--
  141. }
  142. }
  143. // If this is a content line, or if there is content before the fence:
  144. if (!closingFence || lineContentStart !== lineContentEnd) {
  145. content.push(value.slice(lineContentStart, lineContentEnd))
  146. }
  147. if (closingFence) {
  148. break
  149. }
  150. index = lineEnd + 1
  151. lineEnd = value.indexOf(lineFeedChar, index + 1)
  152. lineEnd = lineEnd === -1 ? length : lineEnd
  153. }
  154. content = content.join('\n')
  155. return eat(value.slice(0, lineEnd))({
  156. type: 'math',
  157. value: content,
  158. data: {
  159. hName: 'div',
  160. hProperties: {className: classList.concat()},
  161. hChildren: [{type: 'text', value: content}]
  162. }
  163. })
  164. }
  165. }
  166. function attachCompiler(compiler) {
  167. const proto = compiler.prototype
  168. proto.visitors.math = compileBlockMath
  169. function compileBlockMath(node) {
  170. return '$$\n' + node.value + '\n$$'
  171. }
  172. }