blockquote.mjs 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. // Block quotes
  2. import { isSpace } from '../common/utils.mjs'
  3. export default function blockquote (state, startLine, endLine, silent) {
  4. let pos = state.bMarks[startLine] + state.tShift[startLine]
  5. let max = state.eMarks[startLine]
  6. const oldLineMax = state.lineMax
  7. // if it's indented more than 3 spaces, it should be a code block
  8. if (state.sCount[startLine] - state.blkIndent >= 4) { return false }
  9. // check the block quote marker
  10. if (state.src.charCodeAt(pos) !== 0x3E/* > */) { return false }
  11. // we know that it's going to be a valid blockquote,
  12. // so no point trying to find the end of it in silent mode
  13. if (silent) { return true }
  14. const oldBMarks = []
  15. const oldBSCount = []
  16. const oldSCount = []
  17. const oldTShift = []
  18. const terminatorRules = state.md.block.ruler.getRules('blockquote')
  19. const oldParentType = state.parentType
  20. state.parentType = 'blockquote'
  21. let lastLineEmpty = false
  22. let nextLine
  23. // Search the end of the block
  24. //
  25. // Block ends with either:
  26. // 1. an empty line outside:
  27. // ```
  28. // > test
  29. //
  30. // ```
  31. // 2. an empty line inside:
  32. // ```
  33. // >
  34. // test
  35. // ```
  36. // 3. another tag:
  37. // ```
  38. // > test
  39. // - - -
  40. // ```
  41. for (nextLine = startLine; nextLine < endLine; nextLine++) {
  42. // check if it's outdented, i.e. it's inside list item and indented
  43. // less than said list item:
  44. //
  45. // ```
  46. // 1. anything
  47. // > current blockquote
  48. // 2. checking this line
  49. // ```
  50. const isOutdented = state.sCount[nextLine] < state.blkIndent
  51. pos = state.bMarks[nextLine] + state.tShift[nextLine]
  52. max = state.eMarks[nextLine]
  53. if (pos >= max) {
  54. // Case 1: line is not inside the blockquote, and this line is empty.
  55. break
  56. }
  57. if (state.src.charCodeAt(pos++) === 0x3E/* > */ && !isOutdented) {
  58. // This line is inside the blockquote.
  59. // set offset past spaces and ">"
  60. let initial = state.sCount[nextLine] + 1
  61. let spaceAfterMarker
  62. let adjustTab
  63. // skip one optional space after '>'
  64. if (state.src.charCodeAt(pos) === 0x20 /* space */) {
  65. // ' > test '
  66. // ^ -- position start of line here:
  67. pos++
  68. initial++
  69. adjustTab = false
  70. spaceAfterMarker = true
  71. } else if (state.src.charCodeAt(pos) === 0x09 /* tab */) {
  72. spaceAfterMarker = true
  73. if ((state.bsCount[nextLine] + initial) % 4 === 3) {
  74. // ' >\t test '
  75. // ^ -- position start of line here (tab has width===1)
  76. pos++
  77. initial++
  78. adjustTab = false
  79. } else {
  80. // ' >\t test '
  81. // ^ -- position start of line here + shift bsCount slightly
  82. // to make extra space appear
  83. adjustTab = true
  84. }
  85. } else {
  86. spaceAfterMarker = false
  87. }
  88. let offset = initial
  89. oldBMarks.push(state.bMarks[nextLine])
  90. state.bMarks[nextLine] = pos
  91. while (pos < max) {
  92. const ch = state.src.charCodeAt(pos)
  93. if (isSpace(ch)) {
  94. if (ch === 0x09) {
  95. offset += 4 - (offset + state.bsCount[nextLine] + (adjustTab ? 1 : 0)) % 4
  96. } else {
  97. offset++
  98. }
  99. } else {
  100. break
  101. }
  102. pos++
  103. }
  104. lastLineEmpty = pos >= max
  105. oldBSCount.push(state.bsCount[nextLine])
  106. state.bsCount[nextLine] = state.sCount[nextLine] + 1 + (spaceAfterMarker ? 1 : 0)
  107. oldSCount.push(state.sCount[nextLine])
  108. state.sCount[nextLine] = offset - initial
  109. oldTShift.push(state.tShift[nextLine])
  110. state.tShift[nextLine] = pos - state.bMarks[nextLine]
  111. continue
  112. }
  113. // Case 2: line is not inside the blockquote, and the last line was empty.
  114. if (lastLineEmpty) { break }
  115. // Case 3: another tag found.
  116. let terminate = false
  117. for (let i = 0, l = terminatorRules.length; i < l; i++) {
  118. if (terminatorRules[i](state, nextLine, endLine, true)) {
  119. terminate = true
  120. break
  121. }
  122. }
  123. if (terminate) {
  124. // Quirk to enforce "hard termination mode" for paragraphs;
  125. // normally if you call `tokenize(state, startLine, nextLine)`,
  126. // paragraphs will look below nextLine for paragraph continuation,
  127. // but if blockquote is terminated by another tag, they shouldn't
  128. state.lineMax = nextLine
  129. if (state.blkIndent !== 0) {
  130. // state.blkIndent was non-zero, we now set it to zero,
  131. // so we need to re-calculate all offsets to appear as
  132. // if indent wasn't changed
  133. oldBMarks.push(state.bMarks[nextLine])
  134. oldBSCount.push(state.bsCount[nextLine])
  135. oldTShift.push(state.tShift[nextLine])
  136. oldSCount.push(state.sCount[nextLine])
  137. state.sCount[nextLine] -= state.blkIndent
  138. }
  139. break
  140. }
  141. oldBMarks.push(state.bMarks[nextLine])
  142. oldBSCount.push(state.bsCount[nextLine])
  143. oldTShift.push(state.tShift[nextLine])
  144. oldSCount.push(state.sCount[nextLine])
  145. // A negative indentation means that this is a paragraph continuation
  146. //
  147. state.sCount[nextLine] = -1
  148. }
  149. const oldIndent = state.blkIndent
  150. state.blkIndent = 0
  151. const token_o = state.push('blockquote_open', 'blockquote', 1)
  152. token_o.markup = '>'
  153. const lines = [startLine, 0]
  154. token_o.map = lines
  155. state.md.block.tokenize(state, startLine, nextLine)
  156. const token_c = state.push('blockquote_close', 'blockquote', -1)
  157. token_c.markup = '>'
  158. state.lineMax = oldLineMax
  159. state.parentType = oldParentType
  160. lines[1] = state.line
  161. // Restore original tShift; this might not be necessary since the parser
  162. // has already been here, but just to make sure we can do that.
  163. for (let i = 0; i < oldTShift.length; i++) {
  164. state.bMarks[i + startLine] = oldBMarks[i]
  165. state.tShift[i + startLine] = oldTShift[i]
  166. state.sCount[i + startLine] = oldSCount[i]
  167. state.bsCount[i + startLine] = oldBSCount[i]
  168. }
  169. state.blkIndent = oldIndent
  170. return true
  171. }