link.mjs 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. // Process [link](<to> "stuff")
  2. import { normalizeReference, isSpace } from '../common/utils.mjs'
  3. export default function link (state, silent) {
  4. let code, label, res, ref
  5. let href = ''
  6. let title = ''
  7. let start = state.pos
  8. let parseReference = true
  9. if (state.src.charCodeAt(state.pos) !== 0x5B/* [ */) { return false }
  10. const oldPos = state.pos
  11. const max = state.posMax
  12. const labelStart = state.pos + 1
  13. const labelEnd = state.md.helpers.parseLinkLabel(state, state.pos, true)
  14. // parser failed to find ']', so it's not a valid link
  15. if (labelEnd < 0) { return false }
  16. let pos = labelEnd + 1
  17. if (pos < max && state.src.charCodeAt(pos) === 0x28/* ( */) {
  18. //
  19. // Inline link
  20. //
  21. // might have found a valid shortcut link, disable reference parsing
  22. parseReference = false
  23. // [link]( <href> "title" )
  24. // ^^ skipping these spaces
  25. pos++
  26. for (; pos < max; pos++) {
  27. code = state.src.charCodeAt(pos)
  28. if (!isSpace(code) && code !== 0x0A) { break }
  29. }
  30. if (pos >= max) { return false }
  31. // [link]( <href> "title" )
  32. // ^^^^^^ parsing link destination
  33. start = pos
  34. res = state.md.helpers.parseLinkDestination(state.src, pos, state.posMax)
  35. if (res.ok) {
  36. href = state.md.normalizeLink(res.str)
  37. if (state.md.validateLink(href)) {
  38. pos = res.pos
  39. } else {
  40. href = ''
  41. }
  42. // [link]( <href> "title" )
  43. // ^^ skipping these spaces
  44. start = pos
  45. for (; pos < max; pos++) {
  46. code = state.src.charCodeAt(pos)
  47. if (!isSpace(code) && code !== 0x0A) { break }
  48. }
  49. // [link]( <href> "title" )
  50. // ^^^^^^^ parsing link title
  51. res = state.md.helpers.parseLinkTitle(state.src, pos, state.posMax)
  52. if (pos < max && start !== pos && res.ok) {
  53. title = res.str
  54. pos = res.pos
  55. // [link]( <href> "title" )
  56. // ^^ skipping these spaces
  57. for (; pos < max; pos++) {
  58. code = state.src.charCodeAt(pos)
  59. if (!isSpace(code) && code !== 0x0A) { break }
  60. }
  61. }
  62. }
  63. if (pos >= max || state.src.charCodeAt(pos) !== 0x29/* ) */) {
  64. // parsing a valid shortcut link failed, fallback to reference
  65. parseReference = true
  66. }
  67. pos++
  68. }
  69. if (parseReference) {
  70. //
  71. // Link reference
  72. //
  73. if (typeof state.env.references === 'undefined') { return false }
  74. if (pos < max && state.src.charCodeAt(pos) === 0x5B/* [ */) {
  75. start = pos + 1
  76. pos = state.md.helpers.parseLinkLabel(state, pos)
  77. if (pos >= 0) {
  78. label = state.src.slice(start, pos++)
  79. } else {
  80. pos = labelEnd + 1
  81. }
  82. } else {
  83. pos = labelEnd + 1
  84. }
  85. // covers label === '' and label === undefined
  86. // (collapsed reference link and shortcut reference link respectively)
  87. if (!label) { label = state.src.slice(labelStart, labelEnd) }
  88. ref = state.env.references[normalizeReference(label)]
  89. if (!ref) {
  90. state.pos = oldPos
  91. return false
  92. }
  93. href = ref.href
  94. title = ref.title
  95. }
  96. //
  97. // We found the end of the link, and know for a fact it's a valid link;
  98. // so all that's left to do is to call tokenizer.
  99. //
  100. if (!silent) {
  101. state.pos = labelStart
  102. state.posMax = labelEnd
  103. const token_o = state.push('link_open', 'a', 1)
  104. const attrs = [['href', href]]
  105. token_o.attrs = attrs
  106. if (title) {
  107. attrs.push(['title', title])
  108. }
  109. state.linkLevel++
  110. state.md.inline.tokenize(state)
  111. state.linkLevel--
  112. state.push('link_close', 'a', -1)
  113. }
  114. state.pos = pos
  115. state.posMax = max
  116. return true
  117. }