Skip to content

Commit

Permalink
feat: add no highlight component. (#586)
Browse files Browse the repository at this point in the history
  • Loading branch information
jaywcjlove committed Nov 1, 2023
1 parent bc4be08 commit f471fb3
Show file tree
Hide file tree
Showing 16 changed files with 610 additions and 159 deletions.
28 changes: 28 additions & 0 deletions core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,34 @@ export default function App() {
}
```

### Remove Code Highlight

The following example can help you _exclude code highlighting code_<!--rehype:style=color: #333;background-color: rgb(196 255 122 / 86%);--> from being included in the bundle. `@uiw/react-md-editor/nohighlight`<!--rehype:style=color: #e24444;--> component does not contain the ~~`rehype-prism-plus`~~ code highlighting package, ~~`highlightEnable`~~, ~~`showLineNumbers`~~ and ~~`highlight line`~~ functions will no longer work. ([#586](https://github.com/uiwjs/react-md-editor/issues/586))

```jsx mdx:preview
import React from "react";
import MDEditor from '@uiw/react-md-editor/nohighlight';

const code = `**Hello world!!!**
\`\`\`js
function demo() {}
\`\`\`
`

export default function App() {
const [value, setValue] = React.useState(code);
return (
<div className="container">
<MDEditor
value={value}
onChange={setValue}
/>
<MDEditor.Markdown source={value} style={{ whiteSpace: 'pre-wrap' }} />
</div>
);
}
```

### Custom Toolbars

[![Open in CodeSandbox](https://img.shields.io/badge/Open%20in-CodeSandbox-blue?logo=codesandbox)](https://codesandbox.io/embed/react-md-editor-custom-toolbars-m2n10?fontsize=14&hidenavigation=1&theme=dark)
Expand Down
13 changes: 13 additions & 0 deletions core/nohighlight.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
declare module '@uiw/react-md-editor/nohighlight' {
import MDEditor from '@uiw/react-md-editor/esm/Editor.nohighlight';
import * as commands from '@uiw/react-md-editor/esm/commands';
import * as MarkdownUtil from '@uiw/react-md-editor/esm/utils/markdownUtils';
export * from '@uiw/react-md-editor/esm/commands';
export * from '@uiw/react-md-editor/esm/commands/group';
export * from '@uiw/react-md-editor/esm/utils/markdownUtils';
export * from '@uiw/react-md-editor/esm/utils/InsertTextAtPosition';
export * from '@uiw/react-md-editor/esm/Editor.nohighlight';
export * from '@uiw/react-md-editor/esm/Context';
export { MarkdownUtil, commands };
export default MDEditor;
}
14 changes: 14 additions & 0 deletions core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,20 @@
"author": "kenny wang <[email protected]>",
"main": "lib/index.js",
"module": "esm/index.js",
"exports": {
"./README.md": "./README.md",
"./package.json": "./package.json",
".": {
"import": "./esm/index.js",
"types": "./lib/index.d.ts",
"require": "./lib/index.js"
},
"./nohighlight": {
"import": "./esm/index.nohighlight.js",
"types": "./lib/index.nohighlight.d.ts",
"require": "./lib/index.nohighlight.js"
}
},
"scripts": {
"css:build": "compile-less -d src -o esm",
"css:watch": "compile-less -d src -o esm --watch",
Expand Down
2 changes: 1 addition & 1 deletion core/src/Context.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import { ICommand, TextAreaCommandOrchestrator } from './commands';
import { MDEditorProps } from './Editor';
import { MDEditorProps } from './Types';

export type PreviewType = 'live' | 'edit' | 'preview';

Expand Down
264 changes: 264 additions & 0 deletions core/src/Editor.nohighlight.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,264 @@
import React, { useEffect, useReducer, useMemo, useRef, useImperativeHandle } from 'react';
import MarkdownPreview from '@uiw/react-markdown-preview/nohighlight';
import TextArea from './components/TextArea/index.nohighlight';
import Toolbar from './components/Toolbar';
import DragBar from './components/DragBar';
import { getCommands, getExtraCommands, ICommand, TextState, TextAreaCommandOrchestrator } from './commands';
import { reducer, EditorContext, ContextStore } from './Context';
import type { MDEditorProps } from './Types';

function setGroupPopFalse(data: Record<string, boolean> = {}) {
Object.keys(data).forEach((keyname) => {
data[keyname] = false;
});
return data;
}

export interface RefMDEditor extends ContextStore {}

const InternalMDEditor = React.forwardRef<RefMDEditor, MDEditorProps>(
(props: MDEditorProps, ref: React.ForwardedRef<RefMDEditor>) => {
const {
prefixCls = 'w-md-editor',
className,
value: propsValue,
commands = getCommands(),
commandsFilter,
direction,
extraCommands = getExtraCommands(),
height = 200,
enableScroll = true,
visibleDragbar = typeof props.visiableDragbar === 'boolean' ? props.visiableDragbar : true,
highlightEnable = true,
preview: previewType = 'live',
fullscreen = false,
overflow = true,
previewOptions = {},
textareaProps,
maxHeight = 1200,
minHeight = 100,
autoFocus,
tabSize = 2,
defaultTabEnable = false,
onChange,
onStatistics,
onHeightChange,
hideToolbar,
toolbarBottom = false,
components,
renderTextarea,
...other
} = props || {};
const cmds = commands
.map((item) => (commandsFilter ? commandsFilter(item, false) : item))
.filter(Boolean) as ICommand[];
const extraCmds = extraCommands
.map((item) => (commandsFilter ? commandsFilter(item, true) : item))
.filter(Boolean) as ICommand[];
let [state, dispatch] = useReducer(reducer, {
markdown: propsValue,
preview: previewType,
components,
height,
highlightEnable,
tabSize,
defaultTabEnable,
scrollTop: 0,
scrollTopPreview: 0,
commands: cmds,
extraCommands: extraCmds,
fullscreen,
barPopup: {},
});
const container = useRef<HTMLDivElement>(null);
const previewRef = useRef<HTMLDivElement>(null);
const enableScrollRef = useRef(enableScroll);

useImperativeHandle(ref, () => ({ ...state, container: container.current, dispatch }));
useMemo(() => (enableScrollRef.current = enableScroll), [enableScroll]);
useEffect(() => {
const stateInit: ContextStore = {};
if (container.current) {
stateInit.container = container.current || undefined;
}
stateInit.markdown = propsValue || '';
stateInit.barPopup = {};
if (dispatch) {
dispatch({ ...state, ...stateInit });
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

const cls = [
className,
'wmde-markdown-var',
direction ? `${prefixCls}-${direction}` : null,
prefixCls,
state.preview ? `${prefixCls}-show-${state.preview}` : null,
state.fullscreen ? `${prefixCls}-fullscreen` : null,
]
.filter(Boolean)
.join(' ')
.trim();

useMemo(
() => propsValue !== state.markdown && dispatch({ markdown: propsValue || '' }),
[propsValue, state.markdown],
);
// eslint-disable-next-line react-hooks/exhaustive-deps
useMemo(() => previewType !== state.preview && dispatch({ preview: previewType }), [previewType]);
// eslint-disable-next-line react-hooks/exhaustive-deps
useMemo(() => tabSize !== state.tabSize && dispatch({ tabSize }), [tabSize]);
useMemo(
() => highlightEnable !== state.highlightEnable && dispatch({ highlightEnable }),
// eslint-disable-next-line react-hooks/exhaustive-deps
[highlightEnable],
);
// eslint-disable-next-line react-hooks/exhaustive-deps
useMemo(() => autoFocus !== state.autoFocus && dispatch({ autoFocus: autoFocus }), [autoFocus]);
useMemo(
() => fullscreen !== state.fullscreen && dispatch({ fullscreen: fullscreen }),
// eslint-disable-next-line react-hooks/exhaustive-deps
[fullscreen],
);
// eslint-disable-next-line react-hooks/exhaustive-deps
useMemo(() => height !== state.height && dispatch({ height: height }), [height]);
useMemo(
() => height !== state.height && onHeightChange && onHeightChange(state.height, height, state),
[height, onHeightChange, state],
);
// eslint-disable-next-line react-hooks/exhaustive-deps
useMemo(() => commands !== state.commands && dispatch({ commands: cmds }), [props.commands]);
// eslint-disable-next-line react-hooks/exhaustive-deps
useMemo(
() => extraCommands !== state.extraCommands && dispatch({ extraCommands: extraCmds }),
[props.extraCommands],
);

const textareaDomRef = useRef<HTMLDivElement>();
const active = useRef<'text' | 'preview'>('preview');
const initScroll = useRef(false);

useMemo(() => {
textareaDomRef.current = state.textareaWarp;
if (state.textareaWarp) {
state.textareaWarp.addEventListener('mouseover', () => {
active.current = 'text';
});
state.textareaWarp.addEventListener('mouseleave', () => {
active.current = 'preview';
});
}
}, [state.textareaWarp]);

const handleScroll = (e: React.UIEvent<HTMLDivElement>, type: 'text' | 'preview') => {
if (!enableScrollRef.current) return;
const textareaDom = textareaDomRef.current;
const previewDom = previewRef.current ? previewRef.current : undefined;
if (!initScroll.current) {
active.current = type;
initScroll.current = true;
}
if (textareaDom && previewDom) {
const scale =
(textareaDom.scrollHeight - textareaDom.offsetHeight) / (previewDom.scrollHeight - previewDom.offsetHeight);
if (e.target === textareaDom && active.current === 'text') {
previewDom.scrollTop = textareaDom.scrollTop / scale;
}
if (e.target === previewDom && active.current === 'preview') {
textareaDom.scrollTop = previewDom.scrollTop * scale;
}
let scrollTop = 0;
if (active.current === 'text') {
scrollTop = textareaDom.scrollTop || 0;
} else if (active.current === 'preview') {
scrollTop = previewDom.scrollTop || 0;
}
dispatch({ scrollTop });
}
};

const previewClassName = `${prefixCls}-preview ${previewOptions.className || ''}`;
const handlePreviewScroll = (e: React.UIEvent<HTMLDivElement, UIEvent>) => handleScroll(e, 'preview');
let mdPreview = useMemo(
() => (
<div ref={previewRef} className={previewClassName}>
<MarkdownPreview {...previewOptions} onScroll={handlePreviewScroll} source={state.markdown || ''} />
</div>
),
[previewClassName, previewOptions, state.markdown],
);
const preview = components?.preview && components?.preview(state.markdown || '', state, dispatch);
if (preview && React.isValidElement(preview)) {
mdPreview = (
<div className={previewClassName} ref={previewRef} onScroll={handlePreviewScroll}>
{preview}
</div>
);
}

const containerStyle = { ...other.style, height: state.height || '100%' };
const containerClick = () => dispatch({ barPopup: { ...setGroupPopFalse(state.barPopup) } });
const dragBarChange = (newHeight: number) => dispatch({ height: newHeight });

const changeHandle = (evn: React.ChangeEvent<HTMLTextAreaElement>) => {
onChange && onChange(evn.target.value, evn, state);
if (textareaProps && textareaProps.onChange) {
textareaProps.onChange(evn);
}
if (state.textarea && state.textarea instanceof HTMLTextAreaElement && onStatistics) {
const obj = new TextAreaCommandOrchestrator(state.textarea!);
const objState = (obj.getState() || {}) as TextState;
onStatistics({
...objState,
lineCount: evn.target.value.split('\n').length,
length: evn.target.value.length,
});
}
};
return (
<EditorContext.Provider value={{ ...state, dispatch }}>
<div ref={container} className={cls} {...other} onClick={containerClick} style={containerStyle}>
{!hideToolbar && !toolbarBottom && (
<Toolbar prefixCls={prefixCls} overflow={overflow} toolbarBottom={toolbarBottom} />
)}
<div className={`${prefixCls}-content`}>
{/(edit|live)/.test(state.preview || '') && (
<TextArea
className={`${prefixCls}-input`}
prefixCls={prefixCls}
autoFocus={autoFocus}
{...textareaProps}
onChange={changeHandle}
renderTextarea={components?.textarea || renderTextarea}
onScroll={(e) => handleScroll(e, 'text')}
/>
)}
{/(live|preview)/.test(state.preview || '') && mdPreview}
</div>
{visibleDragbar && !state.fullscreen && (
<DragBar
prefixCls={prefixCls}
height={state.height as number}
maxHeight={maxHeight!}
minHeight={minHeight!}
onChange={dragBarChange}
/>
)}
{!hideToolbar && toolbarBottom && (
<Toolbar prefixCls={prefixCls} overflow={overflow} toolbarBottom={toolbarBottom} />
)}
</div>
</EditorContext.Provider>
);
},
);

type EditorComponent = typeof InternalMDEditor & {
Markdown: typeof MarkdownPreview;
};

const Editor = InternalMDEditor as EditorComponent;
Editor.Markdown = MarkdownPreview;

export default Editor;
Loading

0 comments on commit f471fb3

Please sign in to comment.