extract-sections.ts 2.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687
  1. import type { Schema as SanitizeOption } from 'hast-util-sanitize';
  2. import type { Plugin } from 'unified';
  3. import type { Parent, Node } from 'unist';
  4. import { findAfter } from 'unist-util-find-after';
  5. import { visit } from 'unist-util-visit';
  6. function wrapWithSection(parentNode: Parent, startElem: Node, endElem: Node | null, isDarkMode?: boolean): void {
  7. const siblings = parentNode.children;
  8. const startIndex = siblings.indexOf(startElem);
  9. const endIndex = endElem != null ? siblings.indexOf(endElem) : undefined;
  10. const between = siblings.slice(
  11. startIndex,
  12. endIndex,
  13. );
  14. const section = {
  15. type: 'section',
  16. children: between,
  17. data: {
  18. hName: 'section',
  19. hProperties: {
  20. className: isDarkMode ? 'invert' : '',
  21. },
  22. },
  23. };
  24. siblings.splice(startIndex, between.length, section);
  25. }
  26. function removeElement(parentNode: Parent, elem: Node): void {
  27. const siblings = parentNode.children;
  28. siblings.splice(siblings.indexOf(elem), 1);
  29. }
  30. export type ExtractSectionsPluginParams = {
  31. isDarkMode?: boolean,
  32. disableSeparationByHeader?: boolean,
  33. }
  34. export const remarkPlugin: Plugin<[ExtractSectionsPluginParams]> = (options) => {
  35. const { isDarkMode, disableSeparationByHeader } = options;
  36. const startCondition = (node: Node) => {
  37. if (!disableSeparationByHeader && node.type === 'heading') {
  38. return true;
  39. }
  40. return node.type !== 'thematicBreak';
  41. };
  42. const endCondition = (node: Node) => {
  43. if (!disableSeparationByHeader && node.type === 'heading') {
  44. return true;
  45. }
  46. return node.type === 'thematicBreak';
  47. };
  48. return (tree) => {
  49. // wrap with <section>
  50. visit(
  51. tree,
  52. startCondition,
  53. (node, index, parent: Parent) => {
  54. if (parent == null || parent.type !== 'root' || node.type === 'yaml') {
  55. return;
  56. }
  57. const startElem = node;
  58. const endElem = findAfter(parent, startElem, endCondition);
  59. wrapWithSection(parent, startElem, endElem, isDarkMode);
  60. // remove <hr>
  61. if (endElem != null && endElem.type === 'thematicBreak') {
  62. removeElement(parent, endElem);
  63. }
  64. },
  65. );
  66. };
  67. };
  68. export const sanitizeOption: SanitizeOption = {
  69. // tagNames: ['slides', 'slide'],
  70. };