TextPanel.tsx 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. // Libraries
  2. import { css, cx } from '@emotion/css';
  3. import DangerouslySetHtmlContent from 'dangerously-set-html-content';
  4. import { debounce } from 'lodash';
  5. import React, { PureComponent } from 'react';
  6. import { PanelProps, renderTextPanelMarkdown, textUtil } from '@grafana/data';
  7. // Utils
  8. import { CustomScrollbar, stylesFactory } from '@grafana/ui';
  9. import config from 'app/core/config';
  10. // Types
  11. import { PanelOptions, TextMode } from './models.gen';
  12. interface Props extends PanelProps<PanelOptions> {}
  13. interface State {
  14. html: string;
  15. }
  16. export class TextPanel extends PureComponent<Props, State> {
  17. constructor(props: Props) {
  18. super(props);
  19. this.state = {
  20. html: this.processContent(props.options),
  21. };
  22. }
  23. updateHTML = debounce(() => {
  24. const html = this.processContent(this.props.options);
  25. if (html !== this.state.html) {
  26. this.setState({ html });
  27. }
  28. }, 150);
  29. componentDidUpdate(prevProps: Props) {
  30. // Since any change could be referenced in a template variable,
  31. // This needs to process every time (with debounce)
  32. this.updateHTML();
  33. }
  34. prepareHTML(html: string): string {
  35. const result = this.interpolateString(html);
  36. return config.disableSanitizeHtml ? result : this.sanitizeString(result);
  37. }
  38. prepareMarkdown(content: string): string {
  39. // Always interpolate variables before converting to markdown
  40. // because `marked` replaces '{' and '}' in URLs with '%7B' and '%7D'
  41. // See https://marked.js.org/demo
  42. let result = this.interpolateString(content);
  43. if (config.disableSanitizeHtml) {
  44. result = renderTextPanelMarkdown(result, {
  45. noSanitize: true,
  46. });
  47. return result;
  48. }
  49. result = renderTextPanelMarkdown(result);
  50. return this.sanitizeString(result);
  51. }
  52. interpolateString(content: string): string {
  53. const { replaceVariables } = this.props;
  54. return replaceVariables(content, {}, 'html');
  55. }
  56. sanitizeString(content: string): string {
  57. return textUtil.sanitizeTextPanelContent(content);
  58. }
  59. processContent(options: PanelOptions): string {
  60. const { mode, content } = options;
  61. if (!content) {
  62. return '';
  63. }
  64. if (mode === TextMode.HTML) {
  65. return this.prepareHTML(content);
  66. }
  67. return this.prepareMarkdown(content);
  68. }
  69. render() {
  70. const { html } = this.state;
  71. const styles = getStyles();
  72. return (
  73. <CustomScrollbar autoHeightMin="100%">
  74. <DangerouslySetHtmlContent html={html} className={cx('markdown-html', styles.content)} />
  75. </CustomScrollbar>
  76. );
  77. }
  78. }
  79. const getStyles = stylesFactory(() => {
  80. return {
  81. content: css`
  82. height: 100%;
  83. `,
  84. };
  85. });