Skip to content

Commit

Permalink
Merge pull request #18725 from donotlb/fix-copy-function
Browse files Browse the repository at this point in the history
fix: avoid including line numbers when copying the code
  • Loading branch information
ndelangen authored Aug 23, 2022
2 parents a0a1dd9 + 35520d7 commit 78dc04f
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ export interface SyntaxHighlighterRendererProps {
useInlineStyles: boolean;
}

export type SyntaxHighlighterRenderer = (props: SyntaxHighlighterRendererProps) => ReactNode;

export interface SyntaxHighlighterCustomProps {
language: string;
copyable?: boolean;
Expand All @@ -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;
Expand Down
107 changes: 81 additions & 26 deletions code/lib/components/src/syntaxhighlighter/syntaxhighlighter.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,4 @@
import React, {
ClipboardEvent,
ComponentProps,
FC,
MouseEvent,
useCallback,
useState,
} from 'react';
import React, { ComponentProps, FC, MouseEvent, useCallback, useState } from 'react';
import { logger } from '@storybook/client-logger';
import { styled } from '@storybook/theming';
import global from 'global';
Expand Down Expand Up @@ -36,11 +29,17 @@ 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';

import type { SyntaxHighlighterProps } from './syntaxhighlighter-types';
import type {
SyntaxHighlighterProps,
SyntaxHighlighterRenderer,
SyntaxHighlighterRendererProps,
} from './syntaxhighlighter-types';

const { navigator, document, window: globalWindow } = global;

Expand Down Expand Up @@ -83,6 +82,7 @@ export function createCopyToClipboardFunction() {
export interface WrapperProps {
bordered?: boolean;
padded?: boolean;
showLineNumbers?: boolean;
}

const Wrapper = styled.div<WrapperProps>(
Expand All @@ -98,6 +98,15 @@ const Wrapper = styled.div<WrapperProps>(
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)',
},
}
: {}
);

Expand Down Expand Up @@ -138,6 +147,52 @@ 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 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 defaultRenderer: SyntaxHighlighterRenderer = ({ rows, stylesheet, useInlineStyles }) => {
return rows.map((node: any, i: number) => {
return createElement({
node: processLineNumber(node),
stylesheet,
useInlineStyles,
key: `code-segement${i}`,
});
});
};

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;
}
Expand All @@ -163,25 +218,24 @@ export const SyntaxHighlighter: FC<SyntaxHighlighterProps> = ({
const highlightableCode = formatter ? formatter(format, children) : children.trim();
const [copied, setCopied] = useState(false);

const onClick = useCallback(
(e: MouseEvent<HTMLButtonElement> | ClipboardEvent<HTMLDivElement>) => {
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<HTMLButtonElement>) => {
e.preventDefault();
copyToClipboard(highlightableCode)
.then(() => {
setCopied(true);
globalWindow.setTimeout(() => setCopied(false), 1500);
})
.catch(logger.error);
}, []);
const renderer = wrapRenderer(rest.renderer, showLineNumbers);

return (
<Wrapper bordered={bordered} padded={padded} className={className} onCopyCapture={onClick}>
<Wrapper
bordered={bordered}
padded={padded}
showLineNumbers={showLineNumbers}
className={className}
>
<Scroller>
<ReactSyntaxHighlighter
padded={padded || bordered}
Expand All @@ -193,6 +247,7 @@ export const SyntaxHighlighter: FC<SyntaxHighlighterProps> = ({
CodeTag={Code}
lineNumberContainerStyle={{}}
{...rest}
renderer={renderer}
>
{highlightableCode}
</ReactSyntaxHighlighter>
Expand Down

0 comments on commit 78dc04f

Please sign in to comment.