Skip to content

Commit

Permalink
Add types to markdown editor (#3703)
Browse files Browse the repository at this point in the history
* Plugin types

* Rehype / hast / unist node types

* Make typescript happy with markdown editor and dependencies

* Types for markdown AST nodes and errors

* Don't scan any .d.ts file for internationalization strings
  • Loading branch information
chandlerprall authored Jul 8, 2020
1 parent 779df96 commit 4710782
Show file tree
Hide file tree
Showing 13 changed files with 162 additions and 72 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@
"remark-emoji": "^2.1.0",
"remark-highlight.js": "^5.2.0",
"remark-parse": "^7.0.2",
"remark-rehype": "^6.0.0",
"remark-rehype": "^7.0.0",
"resize-observer-polyfill": "^1.5.0",
"tabbable": "^3.0.0",
"text-diff": "^1.0.1",
Expand Down
2 changes: 1 addition & 1 deletion scripts/babel/fetch-i18n-strings.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ const files = glob.sync(
'**/*.@(js|ts|tsx)',
{ cwd: srcDir, realpath: true },
).filter(filepath => {
if (filepath.endsWith('index.d.ts')) return false;
if (filepath.endsWith('.d.ts')) return false;
if (filepath.endsWith('test.ts')) return false;
if (filepath.endsWith('test.tsx')) return false;
if (filepath.endsWith('test.js')) return false;
Expand Down
8 changes: 8 additions & 0 deletions src/components/markdown_editor/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,11 @@ export {
defaultProcessingPlugins,
} from './markdown_editor';
export { EuiMarkdownContext } from './markdown_context';
export {
EuiMarkdownParseError,
EuiMarkdownAstNode,
EuiMarkdownAstNodePosition,
EuiMarkdownFormatting,
EuiMarkdownEditorUiPlugin,
RemarkRehypeHandler,
} from './markdown_types';
19 changes: 8 additions & 11 deletions src/components/markdown_editor/markdown_actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,11 +166,12 @@ class MarkdownActions {
...defaults,
...incomingStyle,
};
const editor = document.getElementById(this.editorID);
const editor = document.getElementById(
this.editorID
) as HTMLTextAreaElement;

if (editor) {
editor.focus();
// @ts-ignore TODO
styleSelectedText(editor, outgoingStyle);
}
}
Expand Down Expand Up @@ -262,15 +263,11 @@ export function insertText(
* https://hustle.bizongo.in/simulate-react-on-change-on-controlled-components-baa336920e04
*/
const inputEvent = new Event('input', { bubbles: true });
const nativeInputValueSetter =
// @ts-ignore TODO
Object.getOwnPropertyDescriptor(
// @ts-ignore TODO
window.HTMLTextAreaElement.prototype,
'value'
).set;
// @ts-ignore TODO
nativeInputValueSetter.call(textarea, before + text + after);
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(
window.HTMLTextAreaElement.prototype,
'value'
)!.set;
nativeInputValueSetter!.call(textarea, before + text + after);
textarea.dispatchEvent(inputEvent);
}

Expand Down
29 changes: 13 additions & 16 deletions src/components/markdown_editor/markdown_editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,9 @@ import React, {
import unified, { PluggableList, Processor } from 'unified';
import { VFileMessage } from 'vfile-message';
import classNames from 'classnames';
// @ts-ignore TODO
import emoji from 'remark-emoji';
import markdown from 'remark-parse';
// @ts-ignore TODO
import remark2rehype from 'remark-rehype';
// @ts-ignore TODO
import highlight from 'remark-highlight.js';
import rehype2react from 'rehype-react';

Expand All @@ -49,9 +46,13 @@ import { EuiMarkdownFormat } from './markdown_format';
import { EuiMarkdownEditorDropZone } from './markdown_editor_drop_zone';
import { htmlIdGenerator } from '../../services/accessibility';
import { EuiLink } from '../link';
import { EuiCodeBlock } from '../code';
import { EuiCodeBlock, EuiCodeBlockProps } from '../code';
import { MARKDOWN_MODE, MODE_EDITING, MODE_VIEWING } from './markdown_modes';
import { EuiMarkdownEditorUiPlugin } from './markdown_types';
import {
EuiMarkdownAstNode,
EuiMarkdownEditorUiPlugin,
EuiMarkdownParseError,
} from './markdown_types';
import { EuiOverlayMask } from '../overlay_mask';
import { EuiModal } from '../modal';
import { ContextShape, EuiMarkdownContext } from './markdown_context';
Expand Down Expand Up @@ -83,7 +84,7 @@ export const defaultProcessingPlugins: PluggableList = [
createElement: createElement,
components: {
a: EuiLink,
code: (props: any) =>
code: (props: EuiCodeBlockProps) =>
// if has classNames is a codeBlock using highlight js
props.className ? (
<EuiCodeBlock {...props} />
Expand Down Expand Up @@ -121,15 +122,15 @@ type CommonMarkdownEditorProps = HTMLAttributes<HTMLDivElement> &
/** array of toolbar plugins **/
uiPlugins?: EuiMarkdownEditorUiPlugin[];

/** Errors to buble up */
errors?: any;
/** Errors to bubble up */
errors?: EuiMarkdownParseError[];

/** callback triggered when parsing results are available **/
onParse?: (
error: any | null,
error: EuiMarkdownParseError | null,
data: {
messages: VFileMessage[];
ast: any;
ast: EuiMarkdownAstNode;
}
) => void;
};
Expand Down Expand Up @@ -193,7 +194,7 @@ export const EuiMarkdownEditor: FunctionComponent<
}, [parsingPluginList]);

const [parsed, parseError] = useMemo<
[any | null, VFileMessage | null]
[any | null, EuiMarkdownParseError | null]
>(() => {
try {
const parsed = parser.processSync(value);
Expand Down Expand Up @@ -242,7 +243,7 @@ export const EuiMarkdownEditor: FunctionComponent<
const getCursorNode = () => {
const { selectionStart } = textareaRef.current!;

let node: any = parsed.contents;
let node: EuiMarkdownAstNode = parsed.contents;

outer: while (true) {
if (node.children) {
Expand Down Expand Up @@ -335,21 +336,17 @@ export const EuiMarkdownEditor: FunctionComponent<
{createElement(pluginEditorPlugin.editor!, {
node:
selectedNode &&
// @ts-ignore TODO
selectedNode.type === pluginEditorPlugin.name
? selectedNode
: null,
onCancel: () => setPluginEditorPlugin(undefined),
onSave: markdown => {
if (
selectedNode &&
// @ts-ignore TODO
selectedNode.type === pluginEditorPlugin.name
) {
textareaRef.current!.setSelectionRange(
// @ts-ignore TODO
selectedNode.position.start.offset,
// @ts-ignore TODO
selectedNode.position.end.offset
);
}
Expand Down
7 changes: 5 additions & 2 deletions src/components/markdown_editor/markdown_editor_drop_zone.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,14 @@ import React, { FunctionComponent } from 'react';
import classNames from 'classnames';
import { useDropzone } from 'react-dropzone';
import { EuiMarkdownEditorFooter } from './markdown_editor_footer';
import { EuiMarkdownEditorUiPlugin } from './markdown_types';
import {
EuiMarkdownEditorUiPlugin,
EuiMarkdownParseError,
} from './markdown_types';

interface EuiMarkdownEditorDropZoneProps {
uiPlugins: EuiMarkdownEditorUiPlugin[];
errors: any;
errors: EuiMarkdownParseError[];
}

export const EuiMarkdownEditorDropZone: FunctionComponent<
Expand Down
9 changes: 6 additions & 3 deletions src/components/markdown_editor/markdown_editor_footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@ import { EuiOverlayMask } from '../overlay_mask';
import { EuiTitle } from '../title';
import { EuiModal, EuiModalBody, EuiModalHeader } from '../modal';
import { EuiI18n } from '../i18n';
import { EuiMarkdownEditorUiPlugin } from './markdown_types';
import {
EuiMarkdownEditorUiPlugin,
EuiMarkdownParseError,
} from './markdown_types';
import { EuiPopover, EuiPopoverTitle } from '../popover';
import { EuiText } from '../text';
import { EuiSpacer } from '../spacer';
Expand All @@ -41,7 +44,7 @@ interface EuiMarkdownEditorFooterProps {
uiPlugins: EuiMarkdownEditorUiPlugin[];
isUploadingFiles: boolean;
openFiles: () => void;
errors: any;
errors: EuiMarkdownParseError[];
}

export const EuiMarkdownEditorFooter: FunctionComponent<
Expand Down Expand Up @@ -97,7 +100,7 @@ export const EuiMarkdownEditorFooter: FunctionComponent<
default="Errors"
/>
</EuiPopoverTitle>
{errors.map((message: any, idx: any) => (
{errors.map((message, idx) => (
<EuiText key={idx}>{message.toString()}</EuiText>
))}
</div>
Expand Down
11 changes: 8 additions & 3 deletions src/components/markdown_editor/markdown_editor_toolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,12 @@
* under the License.
*/

import React, { FunctionComponent, HTMLAttributes, useContext } from 'react';
import React, {
FunctionComponent,
HTMLAttributes,
MouseEventHandler,
useContext,
} from 'react';
import { CommonProps } from '../common';
import { EuiButtonEmpty, EuiButtonIcon } from '../button';
import { EuiI18n } from '../i18n';
Expand All @@ -26,15 +31,15 @@ import { MARKDOWN_MODE, MODE_VIEWING } from './markdown_modes';
import { EuiMarkdownEditorUiPlugin } from './markdown_types';
import { EuiMarkdownContext } from './markdown_context';
import MarkdownActions from './markdown_actions';
// @ts-ignore TODO
// @ts-ignore a react svg
import MarkdownCheckmarkIcon from './icons/markdown_checkmark';

export type EuiMarkdownEditorToolbarProps = HTMLAttributes<HTMLDivElement> &
CommonProps & {
selectedNode?: null | any;
markdownActions: MarkdownActions;
viewMode: MARKDOWN_MODE;
onClickPreview: any;
onClickPreview: MouseEventHandler<HTMLButtonElement>;
uiPlugins: EuiMarkdownEditorUiPlugin[];
};

Expand Down
33 changes: 27 additions & 6 deletions src/components/markdown_editor/markdown_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,14 @@

import { ComponentType, ReactNode } from 'react';
import { VFile } from 'vfile';
// eslint-disable-next-line import/no-unresolved
import { Node as UnistNode, Position as UnistPosition } from 'unist';
import { Parser } from 'remark-parse';
import { VFileMessage } from 'vfile-message';
import { IconType } from '../icon';

export interface RemarkParser {
Parser: any;
Parser: typeof Parser;
tokenizeInline: Function;
file: VFile;
}
Expand All @@ -38,12 +42,17 @@ export interface RemarkTokenizer {

notInLink?: boolean;
}
export interface RemarkRehypeHandler {
(h: any, node: any): any;
interface RehypeNode {}
interface RemarkRehypeHandlerCallback {
(
node: UnistPosition,
tagName: string,
props: Object,
children: RehypeNode[]
): RehypeNode;
}
export interface AstNodePosition {
start: { line: number; column: number; offset: number };
end: { line: number; column: number; offset: number };
export interface RemarkRehypeHandler {
(h: RemarkRehypeHandlerCallback, node: UnistNode): RehypeNode;
}

export interface EuiMarkdownEditorUiPluginEditorProps {
Expand Down Expand Up @@ -90,3 +99,15 @@ export interface EuiMarkdownFormatting {
orderedList?: boolean;
trimFirst?: boolean;
}

export interface EuiMarkdownAstNode {
type: string;
children?: EuiMarkdownAstNode[];
position: EuiMarkdownAstNodePosition;
}
export interface EuiMarkdownAstNodePosition {
start: { line: number; column: number; offset: number };
end: { line: number; column: number; offset: number };
}

export type EuiMarkdownParseError = string | VFileMessage | Error;
13 changes: 6 additions & 7 deletions src/components/markdown_editor/plugins/markdown_checkbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,16 @@
*/

import React, { FunctionComponent, useContext } from 'react';
// @ts-ignore TODO
import all from 'mdast-util-to-hast/lib/all';
import { EuiCheckbox } from '../../form/checkbox';
import { EuiMarkdownContext } from '../markdown_context';
import { htmlIdGenerator } from '../../../services/accessibility';
import {
AstNodePosition,
RemarkParser,
EuiMarkdownAstNodePosition,
RemarkRehypeHandler,
RemarkTokenizer,
} from '../markdown_types';
import { Plugin } from 'unified';

interface CheckboxNodeDetails {
type: 'checkboxPlugin';
Expand All @@ -37,7 +36,7 @@ interface CheckboxNodeDetails {
isChecked: boolean;
}

function CheckboxParser(this: RemarkParser) {
const CheckboxParser: Plugin = function CheckboxParser() {
const Parser = this.Parser;
const tokenizers = Parser.prototype.blockTokenizers;
const methods = Parser.prototype.blockMethods;
Expand Down Expand Up @@ -80,13 +79,13 @@ function CheckboxParser(this: RemarkParser) {

tokenizers.checkbox = tokenizeCheckbox;
methods.splice(methods.indexOf('list'), 0, 'checkbox'); // Run it just before default `list` plugin to inject our own idea of checkboxes.
}
};

const checkboxMarkdownHandler: RemarkRehypeHandler = (h, node) => {
return h(node.position, 'checkboxPlugin', node, all(h, node));
return h(node.position!, 'checkboxPlugin', node, all(h, node));
};
const CheckboxMarkdownRenderer: FunctionComponent<
CheckboxNodeDetails & { position: AstNodePosition }
CheckboxNodeDetails & { position: EuiMarkdownAstNodePosition }
> = ({ position, lead, label, isChecked, children }) => {
const { replaceNode } = useContext(EuiMarkdownContext);
return (
Expand Down
13 changes: 6 additions & 7 deletions src/components/markdown_editor/plugins/markdown_tooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,15 @@
*/

import React, { FunctionComponent } from 'react';
// @ts-ignore TODO
import all from 'mdast-util-to-hast/lib/all';
import {
AstNodePosition,
RemarkParser,
EuiMarkdownAstNodePosition,
RemarkRehypeHandler,
RemarkTokenizer,
} from '../markdown_types';
import { EuiToolTip } from '../../tool_tip';
import { EuiCodeBlock } from '../../code';
import { Plugin } from 'unified';

interface TooltipNodeDetails {
type: 'tooltipPlugin';
Expand All @@ -52,7 +51,7 @@ const tooltipPlugin = {
),
};

function TooltipParser(this: RemarkParser) {
const TooltipParser: Plugin = function TooltipParser() {
const Parser = this.Parser;
const tokenizers = Parser.prototype.inlineTokenizers;
const methods = Parser.prototype.inlineMethods;
Expand Down Expand Up @@ -135,13 +134,13 @@ function TooltipParser(this: RemarkParser) {

tokenizers.tooltip = tokenizeTooltip;
methods.splice(methods.indexOf('text'), 0, 'tooltip');
}
};

const tooltipMarkdownHandler: RemarkRehypeHandler = (h, node) => {
return h(node.position, 'tooltipPlugin', node, all(h, node));
return h(node.position!, 'tooltipPlugin', node, all(h, node));
};
const tooltipMarkdownRenderer: FunctionComponent<
TooltipNodeDetails & { position: AstNodePosition }
TooltipNodeDetails & { position: EuiMarkdownAstNodePosition }
> = ({ content, children }) => {
return (
<EuiToolTip content={content}>
Expand Down
Loading

0 comments on commit 4710782

Please sign in to comment.