use-slides-by-frontmatter.ts 2.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  1. import { useEffect, useState } from 'react';
  2. import type { Processor } from 'unified';
  3. type ParseResult = {
  4. marp: boolean | undefined,
  5. slide: boolean | undefined,
  6. }
  7. const parseSlideFrontmatter = (frontmatter: string): ParseResult => {
  8. let marp;
  9. let slide;
  10. const lines = frontmatter.split('\n');
  11. lines.forEach((line) => {
  12. const [key, value] = line.split(':').map(part => part.trim());
  13. if (key === 'marp' && value === 'true') {
  14. marp = true;
  15. }
  16. if (key === 'slide' && value === 'true') {
  17. slide = true;
  18. }
  19. });
  20. return { marp, slide };
  21. };
  22. type ProcessorOpts = {
  23. onParsed?: (result: ParseResult) => void,
  24. onSkipped?: () => void,
  25. };
  26. const generateFrontmatterProcessor = async(opts?: ProcessorOpts) => {
  27. const remarkFrontmatter = (await import('remark-frontmatter')).default;
  28. const remarkParse = (await import('remark-parse')).default;
  29. const remarkStringify = (await import('remark-stringify')).default;
  30. const unified = (await import('unified')).unified;
  31. return unified()
  32. .use(remarkParse)
  33. .use(remarkStringify)
  34. .use(remarkFrontmatter, ['yaml'])
  35. .use(() => ((obj) => {
  36. if (obj.children[0]?.type === 'yaml') {
  37. const result = parseSlideFrontmatter(obj.children[0]?.value);
  38. opts?.onParsed?.(result);
  39. }
  40. else {
  41. opts?.onSkipped?.();
  42. }
  43. }));
  44. };
  45. export type UseSlide = {
  46. marp?: boolean,
  47. }
  48. /**
  49. * Frontmatter parser for slide
  50. * @param markdown Markdwon document
  51. * @returns An UseSlide instance. If the markdown does not contain neither "marp" or "slide" attribute in frontmatter, it returns undefined.
  52. */
  53. export const useSlidesByFrontmatter = (markdown?: string, isEnabledMarp?: boolean): UseSlide | undefined => {
  54. const [processor, setProcessor] = useState<Processor|undefined>();
  55. const [parseResult, setParseResult] = useState<UseSlide|undefined>();
  56. useEffect(() => {
  57. if (processor != null) {
  58. return;
  59. }
  60. (async() => {
  61. const p = await generateFrontmatterProcessor({
  62. onParsed: result => setParseResult(result.marp || result.slide ? result : undefined),
  63. onSkipped: () => setParseResult(undefined),
  64. });
  65. setProcessor(p);
  66. })();
  67. }, [processor]);
  68. useEffect(() => {
  69. if (markdown == null || processor == null) {
  70. return;
  71. }
  72. processor.process(markdown);
  73. }, [markdown, processor]);
  74. return parseResult != null
  75. ? { marp: isEnabledMarp && parseResult?.marp }
  76. : undefined;
  77. };