File size: 1,978 Bytes
b39afbe
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
/**
 * Copyright (c) 2023 MERCENARIES.AI PTE. LTD.
 * All rights reserved.
 */

import { ChatRenderer } from 'omni-client-services';
import DOMPurify from 'dompurify';
import { marked, type MarkedOptions } from 'marked';
import { markedEmoji } from 'marked-emoji';

// An extension to render markdown in a chat message

class MarkdownRenderer extends ChatRenderer {
  // opts: https://marked.js.org/using_advanced
  constructor(
    id?: string,
    opts?: { marked?: MarkedOptions; markedEmoji?: { emojis: NonNullable<object>; unicode?: boolean } }
  ) {
    opts ??= {};
    opts.marked ??= { mangle: false, gfm: true, breaks: true, headerIds: false, headerPrefix: undefined };
    opts.markedEmoji ??= { unicode: true, emojis: { emojis: { fire: '🔥', heart: '❤️', thumbsup: '👍' } } };
    super({ id: id ?? 'text/markdown' }, opts);
  }

  async load(): Promise<void> {
    marked.use(markedEmoji(this.opts.markedEmoji));
  }

  render(content: { type: string; value: any }): string {
    // Convert content.value to string
    // If it's an array, join it using Markdown paragraph breaks
    const text = Array.isArray(content.value) ? content.value.join('\n\n') : content.value?.toString();
    const markdownWithHtml = marked.parse(text, { mangle: false, headerIds: false });
    const sanitizedHtml = DOMPurify.sanitize(markdownWithHtml, {
      ALLOWED_TAGS: [
        'h1',
        'h2',
        'h3',
        'h4',
        'h5',
        'h6',
        'blockquote',
        'p',
        'a',
        'ul',
        'ol',
        'nl',
        'li',
        'b',
        'i',
        'strong',
        'em',
        'strike',
        'code',
        'hr',
        'br',
        'div',
        'table',
        'thead',
        'caption',
        'tbody',
        'tr',
        'th',
        'td',
        'pre',
        'img'
      ],
      ALLOWED_ATTR: ['href', 'alt', 'src', 'title']
    });

    return sanitizedHtml;
  }
}

export default MarkdownRenderer;