index.tsx 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. import React from 'react';
  2. import Prism from 'prismjs';
  3. const TOKEN_MARK = 'prism-token';
  4. export function setPrismTokens(language, field, values, alias = 'variable') {
  5. Prism.languages[language][field] = {
  6. alias,
  7. pattern: new RegExp(`(?:^|\\s)(${values.join('|')})(?:$|\\s)`),
  8. };
  9. }
  10. /**
  11. * Code-highlighting plugin based on Prism and
  12. * https://github.com/ianstormtaylor/slate/blob/master/examples/code-highlighting/index.js
  13. *
  14. * (Adapted to handle nested grammar definitions.)
  15. */
  16. export default function PrismPlugin({ definition, language }) {
  17. if (definition) {
  18. // Don't override exising modified definitions
  19. Prism.languages[language] = Prism.languages[language] || definition;
  20. }
  21. return {
  22. /**
  23. * Render a Slate mark with appropiate CSS class names
  24. *
  25. * @param {Object} props
  26. * @return {Element}
  27. */
  28. renderMark(props) {
  29. const { children, mark } = props;
  30. // Only apply spans to marks identified by this plugin
  31. if (mark.type !== TOKEN_MARK) {
  32. return undefined;
  33. }
  34. const className = `token ${mark.data.get('types')}`;
  35. return <span className={className}>{children}</span>;
  36. },
  37. /**
  38. * Decorate code blocks with Prism.js highlighting.
  39. *
  40. * @param {Node} node
  41. * @return {Array}
  42. */
  43. decorateNode(node) {
  44. if (node.type !== 'paragraph') {
  45. return [];
  46. }
  47. const texts = node.getTexts().toArray();
  48. const tstring = texts.map(t => t.text).join('\n');
  49. const grammar = Prism.languages[language];
  50. const tokens = Prism.tokenize(tstring, grammar);
  51. const decorations = [];
  52. let startText = texts.shift();
  53. let endText = startText;
  54. let startOffset = 0;
  55. let endOffset = 0;
  56. let start = 0;
  57. function processToken(token, acc?) {
  58. // Accumulate token types down the tree
  59. const types = `${acc || ''} ${token.type || ''} ${token.alias || ''}`;
  60. // Add mark for token node
  61. if (typeof token === 'string' || typeof token.content === 'string') {
  62. startText = endText;
  63. startOffset = endOffset;
  64. const content = typeof token === 'string' ? token : token.content;
  65. const newlines = content.split('\n').length - 1;
  66. const length = content.length - newlines;
  67. const end = start + length;
  68. let available = startText.text.length - startOffset;
  69. let remaining = length;
  70. endOffset = startOffset + remaining;
  71. while (available < remaining) {
  72. endText = texts.shift();
  73. remaining = length - available;
  74. available = endText.text.length;
  75. endOffset = remaining;
  76. }
  77. // Inject marks from up the tree (acc) as well
  78. if (typeof token !== 'string' || acc) {
  79. const range = {
  80. anchorKey: startText.key,
  81. anchorOffset: startOffset,
  82. focusKey: endText.key,
  83. focusOffset: endOffset,
  84. marks: [{ type: TOKEN_MARK, data: { types } }],
  85. };
  86. decorations.push(range);
  87. }
  88. start = end;
  89. } else if (token.content && token.content.length) {
  90. // Tokens can be nested
  91. for (const subToken of token.content) {
  92. processToken(subToken, types);
  93. }
  94. }
  95. }
  96. // Process top-level tokens
  97. for (const token of tokens) {
  98. processToken(token);
  99. }
  100. return decorations;
  101. },
  102. };
  103. }