Skip to content

Commit

Permalink
feat: add no highlight component.
Browse files Browse the repository at this point in the history
  • Loading branch information
jaywcjlove committed Nov 1, 2023
1 parent ad3bd79 commit 2836e47
Show file tree
Hide file tree
Showing 9 changed files with 250 additions and 109 deletions.
61 changes: 61 additions & 0 deletions core/README.md
Expand Up @@ -147,6 +147,67 @@ export default function Demo() {
}
```

## Code Highlight

```jsx mdx:preview
import React from 'react';
import MarkdownPreview from '@uiw/react-markdown-preview';

const source = `
\`\`\`js
function () {
console.log('hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello')
}
\`\`\`
\`\`\`js
function () {
console.log('hello ')
}
\`\`\`
`;

export default function Demo() {
return (
<MarkdownPreview source={source} />
);
}
```

## 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-markdown-preview/nohighlight`<!--rehype:style=color: #e24444;--> component does not contain the `rehype-prism-plus` code highlighting package, `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 MarkdownPreview from '@uiw/react-markdown-preview/nohighlight';

const source = `
\`\`\`js showLineNumbers
function () {
console.log('hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello')
}
\`\`\`
\`\`\`js showLineNumbers {2}
function () {
console.log('hello ')
}
\`\`\`
`;

export default function Demo() {
return (
<MarkdownPreview
source={source}
rehypeRewrite={(node, index, parent) => {
if (node.tagName === "a" && parent && /^h(1|2|3|4|5|6)/.test(parent.tagName)) {
parent.children = parent.children.slice(1)
}
}}
/>
);
}
```

## Ignore

Ignore content display via HTML comments, Shown in GitHub readme, excluded in HTML.
Expand Down
32 changes: 32 additions & 0 deletions core/nohighlight.d.ts
@@ -0,0 +1,32 @@
declare module '@uiw/react-markdown-preview/nohighlight' {
import React from 'react';
import { Options } from 'react-markdown';
import { PluggableList } from 'unified';
import { RehypeRewriteOptions } from 'rehype-rewrite';
export interface MarkdownPreviewProps extends Omit<Options, 'children'> {
prefixCls?: string;
className?: string;
source?: string;
disableCopy?: boolean;
style?: React.CSSProperties;
pluginsFilter?: (type: 'rehype' | 'remark', plugin: PluggableList) => PluggableList;
wrapperElement?: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement> & {
'data-color-mode'?: 'light' | 'dark';
};
/**
* Please use wrapperElement, Will be removed in v5 release.
* @deprecated
*/
warpperElement?: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement> & {
'data-color-mode'?: 'light' | 'dark';
};
onScroll?: (e: React.UIEvent<HTMLDivElement>) => void;
onMouseOver?: (e: React.MouseEvent<HTMLDivElement>) => void;
rehypeRewrite?: RehypeRewriteOptions['rewrite'];
}
export interface MarkdownPreviewRef extends MarkdownPreviewProps {
mdp: React.RefObject<HTMLDivElement>;
}
const _default: React.ForwardRefExoticComponent<MarkdownPreviewProps & React.RefAttributes<MarkdownPreviewRef>>;
export default _default;
}
14 changes: 14 additions & 0 deletions core/package.json
Expand Up @@ -5,6 +5,20 @@
"homepage": "https://uiwjs.github.io/react-markdown-preview",
"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/nohighlight.js",
"types": "./lib/nohighlight.d.ts",
"require": "./lib/nohighlight.js"
}
},
"scripts": {
"css:build": "compile-less -d src -o esm",
"css:watch": "compile-less -d src -o esm --watch",
Expand Down
115 changes: 9 additions & 106 deletions core/src/index.tsx
@@ -1,117 +1,20 @@
import React, { useImperativeHandle } from 'react';
import ReactMarkdown, { Options } from 'react-markdown';
import { Element } from 'hast';
import React from 'react';
import MarkdownPreview, { type MarkdownPreviewProps, type MarkdownPreviewRef } from './preview';
import rehypePrism from 'rehype-prism-plus';
import { PluggableList } from 'unified';
import gfm from 'remark-gfm';
import raw from 'rehype-raw';
import slug from 'rehype-slug';
import headings from 'rehype-autolink-headings';
import rehypeRewrite from 'rehype-rewrite';
import rehypeAttrs from 'rehype-attr';
import rehypeIgnore from 'rehype-ignore';
import rehypePrism from 'rehype-prism-plus';
import rehypeRewrite, { getCodeString, RehypeRewriteOptions } from 'rehype-rewrite';
import { octiconLink } from './nodes/octiconLink';
import { copyElement } from './nodes/copy';
import { useCopied } from './plugins/useCopied';
import './styles/markdown.less';

import { reservedMeta } from './plugins/reservedMeta';

export interface MarkdownPreviewProps extends Omit<Options, 'children'> {
prefixCls?: string;
className?: string;
source?: string;
disableCopy?: boolean;
style?: React.CSSProperties;
pluginsFilter?: (type: 'rehype' | 'remark', plugin: PluggableList) => PluggableList;
wrapperElement?: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement> & {
'data-color-mode'?: 'light' | 'dark';
};
/**
* Please use wrapperElement, Will be removed in v5 release.
* @deprecated
*/
warpperElement?: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement> & {
'data-color-mode'?: 'light' | 'dark';
};
onScroll?: (e: React.UIEvent<HTMLDivElement>) => void;
onMouseOver?: (e: React.MouseEvent<HTMLDivElement>) => void;
rehypeRewrite?: RehypeRewriteOptions['rewrite'];
}

export interface MarkdownPreviewRef extends MarkdownPreviewProps {
mdp: React.RefObject<HTMLDivElement>;
}
import { rehypeRewriteHandle, defaultRehypePlugins } from './rehypePlugins';

export default React.forwardRef<MarkdownPreviewRef, MarkdownPreviewProps>((props, ref) => {
const {
prefixCls = 'wmde-markdown wmde-markdown-color',
className,
source,
style,
disableCopy = false,
skipHtml = true,
onScroll,
onMouseOver,
pluginsFilter,
rehypeRewrite: rewrite,
wrapperElement = {},
warpperElement = {},
...other
} = props;
const mdp = React.useRef<HTMLDivElement>(null);
useImperativeHandle(ref, () => ({ ...props, mdp }), [mdp, props]);
const cls = `${prefixCls || ''} ${className || ''}`;
useCopied(mdp);

const rehypeRewriteHandle: RehypeRewriteOptions['rewrite'] = (node, index, parent) => {
if (node.type === 'element' && parent && parent.type === 'root' && /h(1|2|3|4|5|6)/.test(node.tagName)) {
const child = node.children && (node.children[0] as Element);
if (child && child.properties && child.properties.ariaHidden === 'true') {
child.properties = { class: 'anchor', ...child.properties };
child.children = [octiconLink];
}
}
if (node.type === 'element' && node.tagName === 'pre' && !disableCopy) {
const code = getCodeString(node.children);
node.children.push(copyElement(code));
}
rewrite && rewrite(node, index, parent);
};

const rehypePlugins: PluggableList = [
reservedMeta,
[rehypePrism, { ignoreMissing: true }],
slug,
headings,
rehypeIgnore,
[rehypeRewrite, { rewrite: rehypeRewriteHandle }],
...defaultRehypePlugins,
[rehypeRewrite, { rewrite: rehypeRewriteHandle(props.disableCopy ?? false, props.rehypeRewrite) }],
[rehypeAttrs, { properties: 'attr' }],
...(other.rehypePlugins || []),
...(props.rehypePlugins || []),
];
const customProps: MarkdownPreviewProps = {
allowElement: (element, index, parent) => {
if (other.allowElement) {
return other.allowElement(element, index, parent);
}
return /^[A-Za-z0-9]+$/.test(element.tagName);
},
};
if (skipHtml) {
rehypePlugins.push(raw);
}
const remarkPlugins = [...(other.remarkPlugins || []), gfm];
const wrapperProps = { ...warpperElement, ...wrapperElement };
return (
<div ref={mdp} onScroll={onScroll} onMouseOver={onMouseOver} {...wrapperProps} className={cls} style={style}>
<ReactMarkdown
{...customProps}
{...other}
skipHtml={skipHtml}
rehypePlugins={pluginsFilter ? pluginsFilter('rehype', rehypePlugins) : rehypePlugins}
remarkPlugins={pluginsFilter ? pluginsFilter('remark', remarkPlugins) : remarkPlugins}
children={source || ''}
/>
</div>
);
return <MarkdownPreview {...props} rehypePlugins={rehypePlugins} ref={ref} />;
});
18 changes: 18 additions & 0 deletions core/src/nohighlight.tsx
@@ -0,0 +1,18 @@
import React from 'react';
import MarkdownPreview, { type MarkdownPreviewProps, type MarkdownPreviewRef } from './preview';
import { PluggableList } from 'unified';
import rehypeRewrite from 'rehype-rewrite';
import { reservedMeta } from './plugins/reservedMeta';
import rehypeAttrs from 'rehype-attr';
import { rehypeRewriteHandle, defaultRehypePlugins } from './rehypePlugins';

export default React.forwardRef<MarkdownPreviewRef, MarkdownPreviewProps>((props, ref) => {
const rehypePlugins: PluggableList = [
reservedMeta,
...defaultRehypePlugins,
[rehypeRewrite, { rewrite: rehypeRewriteHandle(props.disableCopy ?? false, props.rehypeRewrite) }],
[rehypeAttrs, { properties: 'attr' }],
...(props.rehypePlugins || []),
];
return <MarkdownPreview {...props} rehypePlugins={rehypePlugins} ref={ref} />;
});
83 changes: 83 additions & 0 deletions core/src/preview.tsx
@@ -0,0 +1,83 @@
import React, { useImperativeHandle } from 'react';
import ReactMarkdown, { Options } from 'react-markdown';
import { PluggableList } from 'unified';
import gfm from 'remark-gfm';
import raw from 'rehype-raw';
import { type RehypeRewriteOptions } from 'rehype-rewrite';
import { useCopied } from './plugins/useCopied';
import './styles/markdown.less';

export interface MarkdownPreviewProps extends Omit<Options, 'children'> {
prefixCls?: string;
className?: string;
source?: string;
disableCopy?: boolean;
style?: React.CSSProperties;
pluginsFilter?: (type: 'rehype' | 'remark', plugin: PluggableList) => PluggableList;
wrapperElement?: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement> & {
'data-color-mode'?: 'light' | 'dark';
};
/**
* Please use wrapperElement, Will be removed in v5 release.
* @deprecated
*/
warpperElement?: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement> & {
'data-color-mode'?: 'light' | 'dark';
};
onScroll?: (e: React.UIEvent<HTMLDivElement>) => void;
onMouseOver?: (e: React.MouseEvent<HTMLDivElement>) => void;
rehypeRewrite?: RehypeRewriteOptions['rewrite'];
}

export interface MarkdownPreviewRef extends MarkdownPreviewProps {
mdp: React.RefObject<HTMLDivElement>;
}

export default React.forwardRef<MarkdownPreviewRef, MarkdownPreviewProps>((props, ref) => {
const {
prefixCls = 'wmde-markdown wmde-markdown-color',
className,
source,
style,
disableCopy = false,
skipHtml = true,
onScroll,
onMouseOver,
pluginsFilter,
rehypeRewrite: rewrite,
wrapperElement = {},
warpperElement = {},
...other
} = props;
const mdp = React.useRef<HTMLDivElement>(null);
useImperativeHandle(ref, () => ({ ...props, mdp }), [mdp, props]);
const cls = `${prefixCls || ''} ${className || ''}`;
useCopied(mdp);

const rehypePlugins: PluggableList = [...(other.rehypePlugins || [])];
const customProps: MarkdownPreviewProps = {
allowElement: (element, index, parent) => {
if (other.allowElement) {
return other.allowElement(element, index, parent);
}
return /^[A-Za-z0-9]+$/.test(element.tagName);
},
};
if (skipHtml) {
rehypePlugins.push(raw);
}
const remarkPlugins = [...(other.remarkPlugins || []), gfm];
const wrapperProps = { ...warpperElement, ...wrapperElement };
return (
<div ref={mdp} onScroll={onScroll} onMouseOver={onMouseOver} {...wrapperProps} className={cls} style={style}>
<ReactMarkdown
{...customProps}
{...other}
skipHtml={skipHtml}
rehypePlugins={pluginsFilter ? pluginsFilter('rehype', rehypePlugins) : rehypePlugins}
remarkPlugins={pluginsFilter ? pluginsFilter('remark', remarkPlugins) : remarkPlugins}
children={source || ''}
/>
</div>
);
});
27 changes: 27 additions & 0 deletions core/src/rehypePlugins.tsx
@@ -0,0 +1,27 @@
import { PluggableList } from 'unified';
import slug from 'rehype-slug';
import headings from 'rehype-autolink-headings';
import rehypeIgnore from 'rehype-ignore';
import { getCodeString, RehypeRewriteOptions } from 'rehype-rewrite';
import { octiconLink } from './nodes/octiconLink';
import { copyElement } from './nodes/copy';
import { Root, Element, RootContent } from 'hast';

export const rehypeRewriteHandle =
(disableCopy: boolean, rewrite?: RehypeRewriteOptions['rewrite']) =>
(node: Root | RootContent, index: number | null, parent: Root | Element | null) => {
if (node.type === 'element' && parent && parent.type === 'root' && /h(1|2|3|4|5|6)/.test(node.tagName)) {
const child = node.children && (node.children[0] as Element);
if (child && child.properties && child.properties.ariaHidden === 'true') {
child.properties = { class: 'anchor', ...child.properties };
child.children = [octiconLink];
}
}
if (node.type === 'element' && node.tagName === 'pre' && !disableCopy) {
const code = getCodeString(node.children);
node.children.push(copyElement(code));
}
rewrite && rewrite(node, index, parent);
};

export const defaultRehypePlugins: PluggableList = [slug, headings, rehypeIgnore];
6 changes: 4 additions & 2 deletions website/package.json
Expand Up @@ -4,7 +4,8 @@
"private": true,
"scripts": {
"build": "kkt build",
"start": "kkt start"
"start": "kkt start",
"map": "source-map-explorer build/static/js/*.js --html build/website-result.html"
},
"dependencies": {
"@uiw/react-markdown-preview-example": "^1.5.5",
Expand All @@ -21,7 +22,8 @@
"markdown-react-code-preview-loader": "^2.1.5",
"prettier": "^2.8.4",
"pretty-quick": "^3.1.3",
"react-test-renderer": "^18.2.0"
"react-test-renderer": "^18.2.0",
"source-map-explorer": "~2.5.2"
},
"eslintConfig": {
"extends": "react-app"
Expand Down

0 comments on commit 2836e47

Please sign in to comment.