mirror of
https://github.com/Kong/insomnia.git
synced 2026-04-22 15:18:27 -04:00
Markdown preview class->fc (#4956)
* transform * remove debounce prop and handle nulls
This commit is contained in:
@@ -101,11 +101,13 @@ export class Dropdown extends PureComponent<DropdownProps, State> {
|
||||
continue;
|
||||
}
|
||||
|
||||
const match = fuzzyMatch(newFilter, listItem.textContent);
|
||||
const match = fuzzyMatch(newFilter, listItem.textContent || '');
|
||||
|
||||
if (!newFilter || match) {
|
||||
const filterIndex = listItem.getAttribute('data-filter-index');
|
||||
filterItems.push(parseInt(filterIndex, 10));
|
||||
if (filterIndex) {
|
||||
filterItems.push(parseInt(filterIndex, 10));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,7 +133,9 @@ export class Dropdown extends PureComponent<DropdownProps, State> {
|
||||
for (const li of this._dropdownList.querySelectorAll('li')) {
|
||||
if (li.hasAttribute('data-filter-index')) {
|
||||
const filterIndex = li.getAttribute('data-filter-index');
|
||||
items.push(parseInt(filterIndex, 10));
|
||||
if (filterIndex) {
|
||||
items.push(parseInt(filterIndex, 10));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ import React, { PureComponent } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { SetRequired } from 'type-fest';
|
||||
|
||||
import { AUTOBIND_CFG, CONTENT_TYPE_JSON, DEBOUNCE_MILLIS } from '../../../../common/constants';
|
||||
import { AUTOBIND_CFG, CONTENT_TYPE_JSON } from '../../../../common/constants';
|
||||
import { database as db } from '../../../../common/database';
|
||||
import { hotKeyRefs } from '../../../../common/hotkeys';
|
||||
import { executeHotKey } from '../../../../common/hotkeys-listener';
|
||||
@@ -811,7 +811,6 @@ export class GraphQLEditor extends PureComponent<Props, State> {
|
||||
dynamicHeight
|
||||
enableNunjucks
|
||||
uniquenessKey={uniquenessKey ? uniquenessKey + '::variables' : undefined}
|
||||
debounceMillis={DEBOUNCE_MILLIS * 4}
|
||||
manualPrettify={false}
|
||||
defaultValue={variables}
|
||||
className={className}
|
||||
|
||||
@@ -86,7 +86,6 @@ export class MarkdownEditor extends PureComponent<Props, State> {
|
||||
enableNunjucks
|
||||
mode={mode || 'text/x-markdown'}
|
||||
placeholder={placeholder}
|
||||
debounceMillis={300}
|
||||
defaultValue={markdown}
|
||||
onChange={this._handleChange}
|
||||
/>
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import { autoBindMethodsForReact } from 'class-autobind-decorator';
|
||||
import classnames from 'classnames';
|
||||
import highlight from 'highlight.js/lib/common';
|
||||
import React, { FC, PureComponent } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import React, { FC, useEffect, useLayoutEffect, useRef, useState } from 'react';
|
||||
|
||||
import { AUTOBIND_CFG } from '../../common/constants';
|
||||
import { clickLink } from '../../common/electron-helpers';
|
||||
import { markdownToHTML } from '../../common/markdown-to-html';
|
||||
import { HandleRender } from '../../common/render';
|
||||
@@ -14,122 +11,60 @@ interface Props {
|
||||
markdown: string;
|
||||
handleRender?: HandleRender;
|
||||
className?: string;
|
||||
debounceMillis?: number;
|
||||
heading?: string;
|
||||
}
|
||||
|
||||
interface State {
|
||||
compiled: string;
|
||||
renderError: string;
|
||||
}
|
||||
export const MarkdownPreview: FC<Props> = ({ markdown, className, heading }) => {
|
||||
const divRef = useRef<HTMLDivElement>(null);
|
||||
const [compiled, setCompiled] = useState('');
|
||||
const [error, setError] = useState('');
|
||||
const { handleRender } = useGatedNunjucks();
|
||||
|
||||
@autoBindMethodsForReact(AUTOBIND_CFG)
|
||||
class MarkdownPreviewInternal extends PureComponent<Props, State> {
|
||||
state: State = {
|
||||
compiled: '',
|
||||
renderError: '',
|
||||
};
|
||||
|
||||
_compileTimeout: NodeJS.Timeout | null = null;
|
||||
_preview: HTMLDivElement | null = null;
|
||||
|
||||
/**
|
||||
* Debounce and compile the markdown (won't debounce first render)
|
||||
*/
|
||||
_compileMarkdown(markdown: string) {
|
||||
if (this._compileTimeout !== null) {
|
||||
clearTimeout(this._compileTimeout);
|
||||
}
|
||||
this._compileTimeout = setTimeout(
|
||||
async () => {
|
||||
try {
|
||||
const { handleRender } = this.props;
|
||||
const rendered = handleRender ? await handleRender(markdown) : markdown;
|
||||
const compiled = markdownToHTML(rendered);
|
||||
this.setState({
|
||||
compiled,
|
||||
renderError: '',
|
||||
});
|
||||
} catch (err) {
|
||||
this.setState({
|
||||
renderError: err.message,
|
||||
compiled: '',
|
||||
});
|
||||
}
|
||||
},
|
||||
this.state.compiled ? this.props.debounceMillis : 0,
|
||||
);
|
||||
}
|
||||
|
||||
_setPreviewRef(preview: HTMLDivElement) {
|
||||
this._preview = preview;
|
||||
}
|
||||
|
||||
_handleClickLink(event: any) {
|
||||
event.preventDefault();
|
||||
clickLink(event.target.getAttribute('href'));
|
||||
}
|
||||
|
||||
_highlightCodeBlocks() {
|
||||
if (!this._preview) {
|
||||
useEffect(() => {
|
||||
let shouldUpdate = true;
|
||||
const fn = async () => {
|
||||
try {
|
||||
const rendered = handleRender ? await handleRender(markdown) : markdown;
|
||||
const compiled = markdownToHTML(rendered);
|
||||
shouldUpdate && setCompiled(compiled);
|
||||
shouldUpdate && setError('');
|
||||
} catch (err) {
|
||||
shouldUpdate && setCompiled('');
|
||||
shouldUpdate && setError(err.message);
|
||||
}
|
||||
};
|
||||
fn();
|
||||
return () => {
|
||||
shouldUpdate = false;
|
||||
};
|
||||
}, [handleRender, markdown]);
|
||||
useLayoutEffect(() => {
|
||||
if (!divRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
const el = ReactDOM.findDOMNode(this._preview);
|
||||
|
||||
// @ts-expect-error -- TSCONVERSION
|
||||
for (const block of el.querySelectorAll('pre > code')) {
|
||||
highlight.highlightElement(block);
|
||||
for (const block of divRef.current.querySelectorAll('pre > code')) {
|
||||
if (block instanceof HTMLElement) {
|
||||
highlight.highlightElement(block);
|
||||
}
|
||||
}
|
||||
|
||||
// @ts-expect-error -- TSCONVERSION
|
||||
for (const a of el.querySelectorAll('a')) {
|
||||
for (const a of divRef.current.querySelectorAll('a')) {
|
||||
a.title = `Open ${a.getAttribute('href')} in browser`;
|
||||
a.removeEventListener('click', this._handleClickLink);
|
||||
a.addEventListener('click', this._handleClickLink);
|
||||
a.removeEventListener('click', _handleClickLink);
|
||||
a.addEventListener('click', _handleClickLink);
|
||||
}
|
||||
}
|
||||
}, [compiled]);
|
||||
const _handleClickLink = (event: any) => {
|
||||
event.preventDefault();
|
||||
clickLink(event.target.getAttribute('href'));
|
||||
};
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this._compileTimeout !== null) {
|
||||
clearTimeout(this._compileTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line camelcase
|
||||
UNSAFE_componentWillMount() {
|
||||
this._compileMarkdown(this.props.markdown);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line camelcase
|
||||
UNSAFE_componentWillReceiveProps(nextProps: Props) {
|
||||
this._compileMarkdown(nextProps.markdown);
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
this._highlightCodeBlocks();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this._highlightCodeBlocks();
|
||||
}
|
||||
|
||||
render() {
|
||||
const { className, heading } = this.props;
|
||||
const { compiled, renderError } = this.state;
|
||||
return (
|
||||
<div ref={this._setPreviewRef} className={classnames('markdown-preview', className)}>
|
||||
{renderError && <p className="notice error no-margin">Failed to render: {renderError}</p>}
|
||||
<div className="selectable">
|
||||
{heading ? <h1 className="markdown-preview__content-title">{heading}</h1> : null}
|
||||
<div className="markdown-preview__content" dangerouslySetInnerHTML={{ __html: compiled }} />
|
||||
</div>
|
||||
return (
|
||||
<div ref={divRef} className={classnames('markdown-preview', className)}>
|
||||
{error ? <p className="notice error no-margin">Failed to render: {error}</p> : null}
|
||||
<div className="selectable">
|
||||
{heading ? <h1 className="markdown-preview__content-title">{heading}</h1> : null}
|
||||
<div className="markdown-preview__content" dangerouslySetInnerHTML={{ __html: compiled }} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const MarkdownPreview: FC<Omit<Props, 'handleRender'>> = props => {
|
||||
const { handleRender } = useGatedNunjucks();
|
||||
return <MarkdownPreviewInternal {...props} handleRender={handleRender}/>;
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -301,7 +301,6 @@ export const RequestPane: FC<Props> = ({
|
||||
<ErrorBoundary errorClassName="font-error pad text-center">
|
||||
<MarkdownPreview
|
||||
heading={request.name}
|
||||
debounceMillis={1000}
|
||||
markdown={request.description}
|
||||
/>
|
||||
</ErrorBoundary>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"lib": ["DOM.Iterable"],
|
||||
"experimentalDecorators": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "react",
|
||||
|
||||
Reference in New Issue
Block a user