slack-message-parser

JavaScript library for parsing Slack message format (mrkdwn)

  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11
  12. 12
  13. 13
  14. 14
  15. 15
  16. 16
  17. 17
  18. 18
  19. 19
  20. 20
  21. 21
  22. 22
  23. 23
  24. 24
  25. 25
  26. 26
  27. 27
  28. 28
  29. 29
  30. 30
  31. 31
  32. 32
  33. 33
  34. 34
  35. 35
  36. 36
  37. 37
  38. 38
  39. 39
  40. 40
  41. 41
  42. 42
  43. 43
  44. 44
  45. 45
  46. 46
  47. 47
  48. 48
  49. 49
  50. 50
  51. 51
  52. 52
  53. 53
  54. 54
  55. 55
  56. 56
  57. 57
  58. 58
  59. 59
  60. 60
  61. 61
  62. 62
  63. 63
  64. 64
  65. 65
  66. 66
  67. 67
  68. 68
  69. 69
  70. 70
  71. 71
  72. 72
  73. 73
  74. 74
  75. 75
  76. 76
  77. 77
  78. 78
  79. 79
  80. 80
  81. 81
  82. 82
  83. 83
  84. 84
  85. 85
  86. 86
  87. 87
  88. 88
  89. 89
  90. 90
  91. 91
// deno-lint-ignore-file no-explicit-any
import { Plugin, Transformer, unified } from "./deps/unified.ts";
import { remarkGfm, remarkParse, remarkRehype } from "./deps/remark.ts";
import {
  rehypePrism,
  rehypeSlug,
  rehypeStringify,
  rehypeToc,
} from "./deps/rehype.ts";
import { SKIP, visit } from "./deps/unist_util_visit.ts";

const rehypeNavOnly: Plugin = () => {
  const transformer: Transformer = (tree) => {
    visit(tree, "element", (node: any, index: number, parent: any) => {
      if (parent?.type === "root" && node.tagName !== "nav") {
        parent.children.splice(index, 1);

        return [SKIP, index];
      }
    });

    visit(
      tree,
      (node: any) => ["text", "raw"].includes(node.type),
      (_node: any, index: number | null, parent: any) => {
        if (parent?.type === "root" && typeof index === "number") {
          parent.children.splice(index, 1);

          return [SKIP, index];
        }
      },
    );
  };

  return transformer;
};

interface ParseResult {
  /**
   * Parsed HTML
   */
  main: string;

  /**
   * Table of contents HTML
   */
  toc: string;
}

interface ParseOptions {
  path: URL | string;
}

const baseProcessor = () =>
  unified().use(remarkParse).use(remarkGfm).use(remarkRehype, {
    allowDangerousHtml: true,
  }).use(rehypeSlug);

const mainProcessor = baseProcessor().use(rehypePrism, {
  alias: {
    "shell": "sh",
  },
}).use(rehypeStringify, {
  allowDangerousHtml: true,
});

const tocProcessor = baseProcessor().use(rehypeToc, {
  headings: ["h1", "h2", "h3"],
}).use(rehypeNavOnly).use(
  rehypeStringify,
);

export async function parseMarkdownDocs(docsMd: string): Promise<string> {
  return (await mainProcessor.process(docsMd)).toString();
}

export async function parseMarkdownFile(
  { path }: ParseOptions,
): Promise<ParseResult> {
  const content = new TextDecoder("utf-8").decode(await Deno.readFile(path));

  const [main, toc] = await Promise.all([
    mainProcessor.process(content),
    tocProcessor.process(content),
  ]);

  return {
    main: main.toString(),
    toc: toc.toString(),
  };
}