diff --git a/public/app/plugins/panel/text2/TextPanel.tsx b/public/app/plugins/panel/text2/TextPanel.tsx new file mode 100644 index 00000000000..26bb2b2dcb9 --- /dev/null +++ b/public/app/plugins/panel/text2/TextPanel.tsx @@ -0,0 +1,93 @@ +import React, { PureComponent } from 'react'; + +import Remarkable from 'remarkable'; +import { sanitize } from 'app/core/utils/text'; +import config from 'app/core/config'; +import { debounce } from 'lodash'; + +// Types +import { TextOptions } from './types'; +import { PanelProps } from '@grafana/ui/src/types'; + +interface Props extends PanelProps {} +interface State { + html: string; +} + +export class TextPanel extends PureComponent { + remarkable: Remarkable; + + constructor(props) { + super(props); + + this.state = { + html: this.processContent(props.options), + }; + } + + updateHTML = debounce(() => { + const html = this.processContent(this.props.options); + if (html !== this.state.html) { + this.setState({ html }); + } + }, 150); + + componentDidUpdate(prevProps: Props) { + // Since any change could be referenced in a template variable, + // This needs to process everytime (with debounce) + this.updateHTML(); + } + + prepareHTML(html: string): string { + const { replaceVariables } = this.props; + + html = config.disableSanitizeHtml ? html : sanitize(html); + try { + return replaceVariables(html); + } catch (e) { + // TODO -- put the error in the header window + console.log('Text panel error: ', e); + return html; + } + } + + prepareText(content: string): string { + return this.prepareHTML( + content + .replace(/&/g, '&') + .replace(/>/g, '>') + .replace(/') + ); + } + + prepareMarkdown(content: string): string { + if (!this.remarkable) { + this.remarkable = new Remarkable(); + } + return this.prepareHTML(this.remarkable.render(content)); + } + + processContent(options: TextOptions): string { + const { mode, content } = options; + + if (!content) { + return ''; + } + + if (mode === 'markdown') { + return this.prepareMarkdown(content); + } + if (mode === 'html') { + return this.prepareHTML(content); + } + + return this.prepareText(content); + } + + render() { + const { html } = this.state; + + return
; + } +} diff --git a/public/app/plugins/panel/text2/TextPanelEditor.tsx b/public/app/plugins/panel/text2/TextPanelEditor.tsx new file mode 100644 index 00000000000..d604b32c82c --- /dev/null +++ b/public/app/plugins/panel/text2/TextPanelEditor.tsx @@ -0,0 +1,32 @@ +import React, { PureComponent } from 'react'; +import { PanelEditorProps, PanelOptionsGroup, Select, SelectOptionItem } from '@grafana/ui'; + +import { TextOptions } from './types'; + +export class TextPanelEditor extends PureComponent> { + modes: SelectOptionItem[] = [ + { value: 'markdown', label: 'Markdown' }, + { value: 'text', label: 'Text' }, + { value: 'html', label: 'HTML' }, + ]; + + onModeChange = (item: SelectOptionItem) => this.props.onOptionsChange({ ...this.props.options, mode: item.value }); + + onContentChange = evt => this.props.onOptionsChange({ ...this.props.options, content: (event.target as any).value }); + + render() { + const { mode, content } = this.props.options; + + return ( + +
+ Mode +