From 9f44d2e1aeb7bf506bbd6a5dd9c05c2d163bd210 Mon Sep 17 00:00:00 2001 From: donotlb Date: Sat, 16 Jul 2022 14:34:44 +0800 Subject: [PATCH 1/5] fix: avoid including line numbers when copying the code --- .../syntaxhighlighter/syntaxhighlighter.tsx | 29 +++++++------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/lib/components/src/syntaxhighlighter/syntaxhighlighter.tsx b/lib/components/src/syntaxhighlighter/syntaxhighlighter.tsx index ba338868ca43..46990d11d82e 100644 --- a/lib/components/src/syntaxhighlighter/syntaxhighlighter.tsx +++ b/lib/components/src/syntaxhighlighter/syntaxhighlighter.tsx @@ -152,25 +152,18 @@ export const SyntaxHighlighter: FunctionComponent = ({ const highlightableCode = formatter ? formatter(format, children) : children.trim(); const [copied, setCopied] = useState(false); - const onClick = useCallback( - (e: MouseEvent | ClipboardEvent) => { - e.preventDefault(); - - const selectedText = globalWindow.getSelection().toString(); - const textToCopy = e.type !== 'click' && selectedText ? selectedText : highlightableCode; - - copyToClipboard(textToCopy) - .then(() => { - setCopied(true); - globalWindow.setTimeout(() => setCopied(false), 1500); - }) - .catch(logger.error); - }, - [] - ); + const onClick = useCallback((e: MouseEvent) => { + e.preventDefault(); + copyToClipboard(highlightableCode) + .then(() => { + setCopied(true); + globalWindow.setTimeout(() => setCopied(false), 1500); + }) + .catch(logger.error); + }, []); return ( - + = ({ {copyable ? ( - + ) : null} ); From df8d5818143aec9c2d780c82784c0c24480df71b Mon Sep 17 00:00:00 2001 From: donotlb Date: Sat, 16 Jul 2022 14:44:37 +0800 Subject: [PATCH 2/5] minor tweaks --- lib/components/src/syntaxhighlighter/syntaxhighlighter.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/components/src/syntaxhighlighter/syntaxhighlighter.tsx b/lib/components/src/syntaxhighlighter/syntaxhighlighter.tsx index 46990d11d82e..da4ee831afa8 100644 --- a/lib/components/src/syntaxhighlighter/syntaxhighlighter.tsx +++ b/lib/components/src/syntaxhighlighter/syntaxhighlighter.tsx @@ -181,7 +181,7 @@ export const SyntaxHighlighter: FunctionComponent = ({ {copyable ? ( - + ) : null} ); From f711a09b78fb8d7e6db697619d0460a7e3b655db Mon Sep 17 00:00:00 2001 From: donotlb Date: Mon, 18 Jul 2022 22:26:43 +0800 Subject: [PATCH 3/5] remove unused imports --- lib/components/src/syntaxhighlighter/syntaxhighlighter.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/components/src/syntaxhighlighter/syntaxhighlighter.tsx b/lib/components/src/syntaxhighlighter/syntaxhighlighter.tsx index da4ee831afa8..50e2988be711 100644 --- a/lib/components/src/syntaxhighlighter/syntaxhighlighter.tsx +++ b/lib/components/src/syntaxhighlighter/syntaxhighlighter.tsx @@ -1,4 +1,4 @@ -import React, { ClipboardEvent, FunctionComponent, MouseEvent, useCallback, useState } from 'react'; +import React, { FunctionComponent, MouseEvent, useCallback, useState } from 'react'; import { logger } from '@storybook/client-logger'; import { styled } from '@storybook/theming'; import global from 'global'; From ecf85d1ef15501b60e405423c8b6a11bb9ce2f7f Mon Sep 17 00:00:00 2001 From: donotlb Date: Sat, 23 Jul 2022 14:55:18 +0800 Subject: [PATCH 4/5] improve copy-pasting behavior --- .../syntaxhighlighter/syntaxhighlighter.tsx | 59 ++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/lib/components/src/syntaxhighlighter/syntaxhighlighter.tsx b/lib/components/src/syntaxhighlighter/syntaxhighlighter.tsx index 50e2988be711..5d6417d9116f 100644 --- a/lib/components/src/syntaxhighlighter/syntaxhighlighter.tsx +++ b/lib/components/src/syntaxhighlighter/syntaxhighlighter.tsx @@ -29,6 +29,8 @@ import typescript from 'react-syntax-highlighter/dist/esm/languages/prism/typesc // @ts-ignore import ReactSyntaxHighlighter from 'react-syntax-highlighter/dist/esm/prism-light'; +// @ts-ignore +import { createElement } from 'react-syntax-highlighter/dist/esm/index'; import { ActionBar } from '../ActionBar/ActionBar'; import { ScrollArea } from '../ScrollArea/ScrollArea'; @@ -76,6 +78,7 @@ export function createCopyToClipboardFunction() { export interface WrapperProps { bordered?: boolean; padded?: boolean; + showLineNumbers?: boolean; } const Wrapper = styled.div( @@ -91,6 +94,15 @@ const Wrapper = styled.div( borderRadius: theme.borderRadius, background: theme.background.content, } + : {}, + ({ showLineNumbers }) => + showLineNumbers + ? { + // use the before pseudo element to display line numbers + '.react-syntax-highlighter-line-number::before': { + content: 'attr(data-line-number)', + }, + } : {} ); @@ -127,6 +139,45 @@ const Code = styled.div(({ theme }) => ({ opacity: 1, })); +/** + * A custom renderer used to process `span.linenumber` element in each line of code, + * which should only be enabled if `showLineNumbers = true` + */ +const renderer = ({ + rows, + stylesheet, + useInlineStyles, +}: { + rows: any[]; + stylesheet: any; + useInlineStyles: any; +}) => { + return rows.map((node: any, i: number) => { + const children = [...node.children]; + const lineNumberNode = children[0]; + const lineNumber = lineNumberNode.children[0].value; + const processedLineNumberNode = { + ...lineNumberNode, + // empty the line-number element + children: [], + properties: { + ...lineNumberNode.properties, + // add a data-line-number attribute to line-number element, so we can access the line number with `content: attr(data-line-number)` + 'data-line-number': lineNumber, + // remove the 'userSelect: none' style, which will produce extra empty lines when copy-pasting in firefox + style: { ...lineNumberNode.properties.style, userSelect: 'auto' }, + }, + }; + children[0] = processedLineNumberNode; + return createElement({ + node: { ...node, children }, + stylesheet, + useInlineStyles, + key: `code-segement${i}`, + }); + }); +}; + export interface SyntaxHighlighterState { copied: boolean; } @@ -163,7 +214,12 @@ export const SyntaxHighlighter: FunctionComponent = ({ }, []); return ( - + = ({ useInlineStyles={false} PreTag={Pre} CodeTag={Code} + renderer={showLineNumbers ? renderer : undefined} lineNumberContainerStyle={{}} {...rest} > From c265e670e66b203004b6dc64a1516bd6358f8f3d Mon Sep 17 00:00:00 2001 From: donotlb Date: Sun, 21 Aug 2022 12:32:02 +0800 Subject: [PATCH 5/5] make sure handling of line number will not be overridden by parent component --- .../syntaxhighlighter-types.ts | 4 +- .../syntaxhighlighter/syntaxhighlighter.tsx | 72 +++++++++++-------- 2 files changed, 45 insertions(+), 31 deletions(-) diff --git a/code/lib/components/src/syntaxhighlighter/syntaxhighlighter-types.ts b/code/lib/components/src/syntaxhighlighter/syntaxhighlighter-types.ts index 7548a537a72e..7b90c5ed9094 100644 --- a/code/lib/components/src/syntaxhighlighter/syntaxhighlighter-types.ts +++ b/code/lib/components/src/syntaxhighlighter/syntaxhighlighter-types.ts @@ -7,6 +7,8 @@ export interface SyntaxHighlighterRendererProps { useInlineStyles: boolean; } +export type SyntaxHighlighterRenderer = (props: SyntaxHighlighterRendererProps) => ReactNode; + export interface SyntaxHighlighterCustomProps { language: string; copyable?: boolean; @@ -15,7 +17,7 @@ export interface SyntaxHighlighterCustomProps { format?: SyntaxHighlighterFormatTypes; formatter?: (type: SyntaxHighlighterFormatTypes, source: string) => string; className?: string; - renderer?: (props: SyntaxHighlighterRendererProps) => ReactNode; + renderer?: SyntaxHighlighterRenderer; } export type SyntaxHighlighterFormatTypes = boolean | 'dedent' | BuiltInParserName; diff --git a/code/lib/components/src/syntaxhighlighter/syntaxhighlighter.tsx b/code/lib/components/src/syntaxhighlighter/syntaxhighlighter.tsx index f04c6a1977af..ec55a4059a2a 100644 --- a/code/lib/components/src/syntaxhighlighter/syntaxhighlighter.tsx +++ b/code/lib/components/src/syntaxhighlighter/syntaxhighlighter.tsx @@ -35,7 +35,11 @@ import { createElement } from 'react-syntax-highlighter/dist/esm/index'; import { ActionBar } from '../ActionBar/ActionBar'; import { ScrollArea } from '../ScrollArea/ScrollArea'; -import type { SyntaxHighlighterProps } from './syntaxhighlighter-types'; +import type { + SyntaxHighlighterProps, + SyntaxHighlighterRenderer, + SyntaxHighlighterRendererProps, +} from './syntaxhighlighter-types'; const { navigator, document, window: globalWindow } = global; @@ -143,38 +147,34 @@ const Code = styled.div(({ theme }) => ({ opacity: 1, })); +const processLineNumber = (row: any) => { + const children = [...row.children]; + const lineNumberNode = children[0]; + const lineNumber = lineNumberNode.children[0].value; + const processedLineNumberNode = { + ...lineNumberNode, + // empty the line-number element + children: [], + properties: { + ...lineNumberNode.properties, + // add a data-line-number attribute to line-number element, so we can access the line number with `content: attr(data-line-number)` + 'data-line-number': lineNumber, + // remove the 'userSelect: none' style, which will produce extra empty lines when copy-pasting in firefox + style: { ...lineNumberNode.properties.style, userSelect: 'auto' }, + }, + }; + children[0] = processedLineNumberNode; + return { ...row, children }; +}; + /** - * A custom renderer used to process `span.linenumber` element in each line of code, - * which should only be enabled if `showLineNumbers = true` + * A custom renderer for handling `span.linenumber` element in each line of code, + * which is enabled by default if no renderer is passed in from the parent component */ -const renderer = ({ - rows, - stylesheet, - useInlineStyles, -}: { - rows: any[]; - stylesheet: any; - useInlineStyles: any; -}) => { +const defaultRenderer: SyntaxHighlighterRenderer = ({ rows, stylesheet, useInlineStyles }) => { return rows.map((node: any, i: number) => { - const children = [...node.children]; - const lineNumberNode = children[0]; - const lineNumber = lineNumberNode.children[0].value; - const processedLineNumberNode = { - ...lineNumberNode, - // empty the line-number element - children: [], - properties: { - ...lineNumberNode.properties, - // add a data-line-number attribute to line-number element, so we can access the line number with `content: attr(data-line-number)` - 'data-line-number': lineNumber, - // remove the 'userSelect: none' style, which will produce extra empty lines when copy-pasting in firefox - style: { ...lineNumberNode.properties.style, userSelect: 'auto' }, - }, - }; - children[0] = processedLineNumberNode; return createElement({ - node: { ...node, children }, + node: processLineNumber(node), stylesheet, useInlineStyles, key: `code-segement${i}`, @@ -182,6 +182,17 @@ const renderer = ({ }); }; +const wrapRenderer = (renderer: SyntaxHighlighterRenderer, showLineNumbers: boolean) => { + if (!showLineNumbers) { + return renderer; + } + if (renderer) { + return ({ rows, ...rest }: SyntaxHighlighterRendererProps) => + renderer({ rows: rows.map((row) => processLineNumber(row)), ...rest }); + } + return defaultRenderer; +}; + export interface SyntaxHighlighterState { copied: boolean; } @@ -216,6 +227,7 @@ export const SyntaxHighlighter: FC = ({ }) .catch(logger.error); }, []); + const renderer = wrapRenderer(rest.renderer, showLineNumbers); return ( = ({ useInlineStyles={false} PreTag={Pre} CodeTag={Code} - renderer={showLineNumbers ? renderer : undefined} lineNumberContainerStyle={{}} {...rest} + renderer={renderer} > {highlightableCode}