From 2836e4735d8dc7a83003d9652588881903dc6195 Mon Sep 17 00:00:00 2001 From: jaywcjlove <398188662@qq.com> Date: Wed, 1 Nov 2023 12:19:56 +0800 Subject: [PATCH] feat: add no highlight component. https://github.com/uiwjs/react-md-editor/issues/586 --- core/README.md | 61 ++++++++++++++++++++ core/nohighlight.d.ts | 32 +++++++++++ core/package.json | 14 +++++ core/src/index.tsx | 115 +++---------------------------------- core/src/nohighlight.tsx | 18 ++++++ core/src/preview.tsx | 83 ++++++++++++++++++++++++++ core/src/rehypePlugins.tsx | 27 +++++++++ website/package.json | 6 +- website/src/index.tsx | 3 +- 9 files changed, 250 insertions(+), 109 deletions(-) create mode 100644 core/nohighlight.d.ts create mode 100644 core/src/nohighlight.tsx create mode 100644 core/src/preview.tsx create mode 100644 core/src/rehypePlugins.tsx diff --git a/core/README.md b/core/README.md index f2bdd948..1838e4b2 100644 --- a/core/README.md +++ b/core/README.md @@ -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 ( + + ); +} +``` + +## Remove Code Highlight + +The following example can help you _exclude code highlighting code_ from being included in the bundle. `@uiw/react-markdown-preview/nohighlight` 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 ( + { + 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. diff --git a/core/nohighlight.d.ts b/core/nohighlight.d.ts new file mode 100644 index 00000000..a6a937a2 --- /dev/null +++ b/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 { + prefixCls?: string; + className?: string; + source?: string; + disableCopy?: boolean; + style?: React.CSSProperties; + pluginsFilter?: (type: 'rehype' | 'remark', plugin: PluggableList) => PluggableList; + wrapperElement?: React.DetailedHTMLProps, HTMLDivElement> & { + 'data-color-mode'?: 'light' | 'dark'; + }; + /** + * Please use wrapperElement, Will be removed in v5 release. + * @deprecated + */ + warpperElement?: React.DetailedHTMLProps, HTMLDivElement> & { + 'data-color-mode'?: 'light' | 'dark'; + }; + onScroll?: (e: React.UIEvent) => void; + onMouseOver?: (e: React.MouseEvent) => void; + rehypeRewrite?: RehypeRewriteOptions['rewrite']; + } + export interface MarkdownPreviewRef extends MarkdownPreviewProps { + mdp: React.RefObject; + } + const _default: React.ForwardRefExoticComponent>; + export default _default; +} diff --git a/core/package.json b/core/package.json index faecb6da..37b511f4 100644 --- a/core/package.json +++ b/core/package.json @@ -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", diff --git a/core/src/index.tsx b/core/src/index.tsx index 51b9a762..5eecf20c 100644 --- a/core/src/index.tsx +++ b/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 { - prefixCls?: string; - className?: string; - source?: string; - disableCopy?: boolean; - style?: React.CSSProperties; - pluginsFilter?: (type: 'rehype' | 'remark', plugin: PluggableList) => PluggableList; - wrapperElement?: React.DetailedHTMLProps, HTMLDivElement> & { - 'data-color-mode'?: 'light' | 'dark'; - }; - /** - * Please use wrapperElement, Will be removed in v5 release. - * @deprecated - */ - warpperElement?: React.DetailedHTMLProps, HTMLDivElement> & { - 'data-color-mode'?: 'light' | 'dark'; - }; - onScroll?: (e: React.UIEvent) => void; - onMouseOver?: (e: React.MouseEvent) => void; - rehypeRewrite?: RehypeRewriteOptions['rewrite']; -} - -export interface MarkdownPreviewRef extends MarkdownPreviewProps { - mdp: React.RefObject; -} +import { rehypeRewriteHandle, defaultRehypePlugins } from './rehypePlugins'; export default React.forwardRef((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(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 ( -
- -
- ); + return ; }); diff --git a/core/src/nohighlight.tsx b/core/src/nohighlight.tsx new file mode 100644 index 00000000..dba8bd47 --- /dev/null +++ b/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((props, ref) => { + const rehypePlugins: PluggableList = [ + reservedMeta, + ...defaultRehypePlugins, + [rehypeRewrite, { rewrite: rehypeRewriteHandle(props.disableCopy ?? false, props.rehypeRewrite) }], + [rehypeAttrs, { properties: 'attr' }], + ...(props.rehypePlugins || []), + ]; + return ; +}); diff --git a/core/src/preview.tsx b/core/src/preview.tsx new file mode 100644 index 00000000..4d406464 --- /dev/null +++ b/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 { + prefixCls?: string; + className?: string; + source?: string; + disableCopy?: boolean; + style?: React.CSSProperties; + pluginsFilter?: (type: 'rehype' | 'remark', plugin: PluggableList) => PluggableList; + wrapperElement?: React.DetailedHTMLProps, HTMLDivElement> & { + 'data-color-mode'?: 'light' | 'dark'; + }; + /** + * Please use wrapperElement, Will be removed in v5 release. + * @deprecated + */ + warpperElement?: React.DetailedHTMLProps, HTMLDivElement> & { + 'data-color-mode'?: 'light' | 'dark'; + }; + onScroll?: (e: React.UIEvent) => void; + onMouseOver?: (e: React.MouseEvent) => void; + rehypeRewrite?: RehypeRewriteOptions['rewrite']; +} + +export interface MarkdownPreviewRef extends MarkdownPreviewProps { + mdp: React.RefObject; +} + +export default React.forwardRef((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(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 ( +
+ +
+ ); +}); diff --git a/core/src/rehypePlugins.tsx b/core/src/rehypePlugins.tsx new file mode 100644 index 00000000..6e2c6d21 --- /dev/null +++ b/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]; diff --git a/website/package.json b/website/package.json index 1a53280e..659260bd 100644 --- a/website/package.json +++ b/website/package.json @@ -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", @@ -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" diff --git a/website/src/index.tsx b/website/src/index.tsx index 3fbec8a9..83265db2 100644 --- a/website/src/index.tsx +++ b/website/src/index.tsx @@ -1,6 +1,7 @@ import { createRoot } from 'react-dom/client'; import MarkdownPreviewExample from '@uiw/react-markdown-preview-example'; import data from '@uiw/react-markdown-preview/README.md'; +import pkg from '@uiw/react-markdown-preview/package.json'; import { Footer, Example } from './App'; const container = document.getElementById('root'); @@ -17,7 +18,7 @@ root.render( } description="React component preview markdown text in web browser. The minimal amount of CSS to replicate the GitHub Markdown style." - version={`v${VERSION}`} + version={`v${pkg.version}`} >