index.js 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. /**
  2. * @import {Root as HastRoot} from 'hast'
  3. * @import {Root as MdastRoot} from 'mdast'
  4. * @import {Options as ToHastOptions} from 'mdast-util-to-hast'
  5. * @import {Processor} from 'unified'
  6. * @import {VFile} from 'vfile'
  7. */
  8. /**
  9. * @typedef {Omit<ToHastOptions, 'file'>} Options
  10. *
  11. * @callback TransformBridge
  12. * Bridge-mode.
  13. *
  14. * Runs the destination with the new hast tree.
  15. * Discards result.
  16. * @param {MdastRoot} tree
  17. * Tree.
  18. * @param {VFile} file
  19. * File.
  20. * @returns {Promise<undefined>}
  21. * Nothing.
  22. *
  23. * @callback TransformMutate
  24. * Mutate-mode.
  25. *
  26. * Further transformers run on the hast tree.
  27. * @param {MdastRoot} tree
  28. * Tree.
  29. * @param {VFile} file
  30. * File.
  31. * @returns {HastRoot}
  32. * Tree (hast).
  33. */
  34. import {toHast} from 'mdast-util-to-hast'
  35. /**
  36. * Turn markdown into HTML.
  37. *
  38. * ##### Notes
  39. *
  40. * ###### Signature
  41. *
  42. * * if a processor is given,
  43. * runs the (rehype) plugins used on it with a hast tree,
  44. * then discards the result (*bridge mode*)
  45. * * otherwise,
  46. * returns a hast tree,
  47. * the plugins used after `remarkRehype` are rehype plugins (*mutate mode*)
  48. *
  49. * > 👉 **Note**:
  50. * > It’s highly unlikely that you want to pass a `processor`.
  51. *
  52. * ###### HTML
  53. *
  54. * Raw HTML is available in mdast as `html` nodes and can be embedded in hast
  55. * as semistandard `raw` nodes.
  56. * Most plugins ignore `raw` nodes but two notable ones don’t:
  57. *
  58. * * `rehype-stringify` also has an option `allowDangerousHtml` which will
  59. * output the raw HTML.
  60. * This is typically discouraged as noted by the option name but is useful if
  61. * you completely trust authors
  62. * * `rehype-raw` can handle the raw embedded HTML strings by parsing them
  63. * into standard hast nodes (`element`, `text`, etc);
  64. * this is a heavy task as it needs a full HTML parser,
  65. * but it is the only way to support untrusted content
  66. *
  67. * ###### Footnotes
  68. *
  69. * Many options supported here relate to footnotes.
  70. * Footnotes are not specified by CommonMark,
  71. * which we follow by default.
  72. * They are supported by GitHub,
  73. * so footnotes can be enabled in markdown with `remark-gfm`.
  74. *
  75. * The options `footnoteBackLabel` and `footnoteLabel` define natural language
  76. * that explains footnotes,
  77. * which is hidden for sighted users but shown to assistive technology.
  78. * When your page is not in English,
  79. * you must define translated values.
  80. *
  81. * Back references use ARIA attributes,
  82. * but the section label itself uses a heading that is hidden with an
  83. * `sr-only` class.
  84. * To show it to sighted users,
  85. * define different attributes in `footnoteLabelProperties`.
  86. *
  87. * ###### Clobbering
  88. *
  89. * Footnotes introduces a problem,
  90. * as it links footnote calls to footnote definitions on the page through `id`
  91. * attributes generated from user content,
  92. * which results in DOM clobbering.
  93. *
  94. * DOM clobbering is this:
  95. *
  96. * ```html
  97. * <p id=x></p>
  98. * <script>alert(x) // `x` now refers to the DOM `p#x` element</script>
  99. * ```
  100. *
  101. * Elements by their ID are made available by browsers on the `window` object,
  102. * which is a security risk.
  103. * Using a prefix solves this problem.
  104. *
  105. * More information on how to handle clobbering and the prefix is explained in
  106. * *Example: headings (DOM clobbering)* in `rehype-sanitize`.
  107. *
  108. * ###### Unknown nodes
  109. *
  110. * Unknown nodes are nodes with a type that isn’t in `handlers` or `passThrough`.
  111. * The default behavior for unknown nodes is:
  112. *
  113. * * when the node has a `value`
  114. * (and doesn’t have `data.hName`, `data.hProperties`, or `data.hChildren`,
  115. * see later),
  116. * create a hast `text` node
  117. * * otherwise,
  118. * create a `<div>` element (which could be changed with `data.hName`),
  119. * with its children mapped from mdast to hast as well
  120. *
  121. * This behavior can be changed by passing an `unknownHandler`.
  122. *
  123. * @overload
  124. * @param {Processor} processor
  125. * @param {Readonly<Options> | null | undefined} [options]
  126. * @returns {TransformBridge}
  127. *
  128. * @overload
  129. * @param {Readonly<Options> | null | undefined} [options]
  130. * @returns {TransformMutate}
  131. *
  132. * @overload
  133. * @param {Readonly<Options> | Processor | null | undefined} [destination]
  134. * @param {Readonly<Options> | null | undefined} [options]
  135. * @returns {TransformBridge | TransformMutate}
  136. *
  137. * @param {Readonly<Options> | Processor | null | undefined} [destination]
  138. * Processor or configuration (optional).
  139. * @param {Readonly<Options> | null | undefined} [options]
  140. * When a processor was given,
  141. * configuration (optional).
  142. * @returns {TransformBridge | TransformMutate}
  143. * Transform.
  144. */
  145. export default function remarkRehype(destination, options) {
  146. if (destination && 'run' in destination) {
  147. /**
  148. * @type {TransformBridge}
  149. */
  150. return async function (tree, file) {
  151. // Cast because root in -> root out.
  152. const hastTree = /** @type {HastRoot} */ (
  153. toHast(tree, {file, ...options})
  154. )
  155. await destination.run(hastTree, file)
  156. }
  157. }
  158. /**
  159. * @type {TransformMutate}
  160. */
  161. return function (tree, file) {
  162. // Cast because root in -> root out.
  163. // To do: in the future, disallow ` || options` fallback.
  164. // With `unified-engine`, `destination` can be `undefined` but
  165. // `options` will be the file set.
  166. // We should not pass that as `options`.
  167. return /** @type {HastRoot} */ (
  168. toHast(tree, {file, ...(destination || options)})
  169. )
  170. }
  171. }