Doug Lauder
2023-03-22 17:22:27 -04:00
коммит произвёл GitHub
родитель b61c096497
Коммит c943ed6859
13276 изменённых файлов: 1695615 добавлений и 223189 удалений

Просмотреть файл

@@ -0,0 +1,638 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`codeBlock should render html code block with proper indentation after syntax highlighting 1`] = `
<IntlProvider
defaultFormats={Object {}}
defaultLocale="en"
formats={Object {}}
locale="en"
messages={Object {}}
onError={[Function]}
textComponent={Symbol(react.fragment)}
>
<CodeBlock
code="\`\`\`html
<div className='myClass'>
<a href='https://randomgibberishurl.com'>ClickMe</a>
</div>
\`\`\`
"
language="html"
>
<div
className="post-code"
>
<div
className="post-code__overlay"
>
<CopyButton
afterCopyText="Copied"
content="\`\`\`html
<div className='myClass'>
<a href='https://randomgibberishurl.com'>ClickMe</a>
</div>
\`\`\`
"
placement="top"
>
<OverlayTrigger
defaultOverlayShown={false}
delayShow={400}
overlay={
<Tooltip
bsClass="tooltip"
id="copyButton"
placement="right"
>
<Memo(MemoizedFormattedMessage)
defaultMessage="Copy code"
id="copy.code.message"
/>
</Tooltip>
}
placement="top"
shouldUpdatePosition={true}
trigger={
Array [
"hover",
"focus",
]
}
>
<OverlayTrigger
defaultOverlayShown={false}
delayShow={400}
overlay={
<OverlayWrapper
bsClass="tooltip"
id="copyButton"
intl={
Object {
"defaultFormats": Object {},
"defaultLocale": "en",
"defaultRichTextElements": undefined,
"formatDate": [Function],
"formatDateTimeRange": [Function],
"formatDateToParts": [Function],
"formatDisplayName": [Function],
"formatList": [Function],
"formatListToParts": [Function],
"formatMessage": [Function],
"formatNumber": [Function],
"formatNumberToParts": [Function],
"formatPlural": [Function],
"formatRelativeTime": [Function],
"formatTime": [Function],
"formatTimeToParts": [Function],
"formats": Object {},
"formatters": Object {
"getDateTimeFormat": [Function],
"getDisplayNames": [Function],
"getListFormat": [Function],
"getMessageFormat": [Function],
"getNumberFormat": [Function],
"getPluralRules": [Function],
"getRelativeTimeFormat": [Function],
},
"locale": "en",
"messages": Object {},
"onError": [Function],
"textComponent": Symbol(react.fragment),
"timeZone": undefined,
"wrapRichTextChunksInFragment": undefined,
}
}
placement="right"
>
<Memo(MemoizedFormattedMessage)
defaultMessage="Copy code"
id="copy.code.message"
/>
</OverlayWrapper>
}
placement="top"
shouldUpdatePosition={true}
trigger={
Array [
"hover",
"focus",
]
}
>
<span
className="post-code__clipboard"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
>
<i
className="icon icon-content-copy"
role="button"
/>
</span>
</OverlayTrigger>
</OverlayTrigger>
</CopyButton>
<span
className="post-code__language"
>
HTML, XML
</span>
</div>
<div
className="hljs"
>
<div
className="post-code__line-numbers"
>
1
2
3
4
5
6
</div>
<code
dangerouslySetInnerHTML={
Object {
"__html": "\`\`\`html
<span class=\\"hljs-tag\\">&lt;<span class=\\"hljs-name\\">div</span> <span class=\\"hljs-attr\\">className</span>=<span class=\\"hljs-string\\">&#x27;myClass&#x27;</span>&gt;</span>
<span class=\\"hljs-tag\\">&lt;<span class=\\"hljs-name\\">a</span> <span class=\\"hljs-attr\\">href</span>=<span class=\\"hljs-string\\">&#x27;https://randomgibberishurl.com&#x27;</span>&gt;</span>ClickMe<span class=\\"hljs-tag\\">&lt;/<span class=\\"hljs-name\\">a</span>&gt;</span>
<span class=\\"hljs-tag\\">&lt;/<span class=\\"hljs-name\\">div</span>&gt;</span>
\`\`\`
",
}
}
/>
</div>
</div>
</CodeBlock>
</IntlProvider>
`;
exports[`codeBlock should render html code block with proper indentation before syntax highlighting 1`] = `
<div
className="post-code"
>
<div
className="post-code__overlay"
>
<CopyButton
afterCopyText="Copied"
content="\`\`\`html
<div className='myClass'>
<a href='https://randomgibberishurl.com'>ClickMe</a>
</div>
\`\`\`
"
placement="top"
/>
<span
className="post-code__language"
>
HTML, XML
</span>
</div>
<div
className="hljs"
>
<div
className="post-code__line-numbers"
>
1
2
3
4
5
6
</div>
<code
dangerouslySetInnerHTML={
Object {
"__html": "\`\`\`html
&lt;div className=&apos;myClass&apos;&gt;
&lt;a href=&apos;https://randomgibberishurl.com&apos;&gt;ClickMe&lt;/a&gt;
&lt;/div&gt;
\`\`\`
",
}
}
/>
</div>
</div>
`;
exports[`codeBlock should render typescript code block after syntax highlighting 1`] = `
<IntlProvider
defaultFormats={Object {}}
defaultLocale="en"
formats={Object {}}
locale="en"
messages={Object {}}
onError={[Function]}
textComponent={Symbol(react.fragment)}
>
<CodeBlock
code="\`\`\`typescript
const myFunction = () => {
console.log('This is a meaningful function');
};
\`\`\`
"
language="typescript"
>
<div
className="post-code"
>
<div
className="post-code__overlay"
>
<CopyButton
afterCopyText="Copied"
content="\`\`\`typescript
const myFunction = () => {
console.log('This is a meaningful function');
};
\`\`\`
"
placement="top"
>
<OverlayTrigger
defaultOverlayShown={false}
delayShow={400}
overlay={
<Tooltip
bsClass="tooltip"
id="copyButton"
placement="right"
>
<Memo(MemoizedFormattedMessage)
defaultMessage="Copy code"
id="copy.code.message"
/>
</Tooltip>
}
placement="top"
shouldUpdatePosition={true}
trigger={
Array [
"hover",
"focus",
]
}
>
<OverlayTrigger
defaultOverlayShown={false}
delayShow={400}
overlay={
<OverlayWrapper
bsClass="tooltip"
id="copyButton"
intl={
Object {
"defaultFormats": Object {},
"defaultLocale": "en",
"defaultRichTextElements": undefined,
"formatDate": [Function],
"formatDateTimeRange": [Function],
"formatDateToParts": [Function],
"formatDisplayName": [Function],
"formatList": [Function],
"formatListToParts": [Function],
"formatMessage": [Function],
"formatNumber": [Function],
"formatNumberToParts": [Function],
"formatPlural": [Function],
"formatRelativeTime": [Function],
"formatTime": [Function],
"formatTimeToParts": [Function],
"formats": Object {},
"formatters": Object {
"getDateTimeFormat": [Function],
"getDisplayNames": [Function],
"getListFormat": [Function],
"getMessageFormat": [Function],
"getNumberFormat": [Function],
"getPluralRules": [Function],
"getRelativeTimeFormat": [Function],
},
"locale": "en",
"messages": Object {},
"onError": [Function],
"textComponent": Symbol(react.fragment),
"timeZone": undefined,
"wrapRichTextChunksInFragment": undefined,
}
}
placement="right"
>
<Memo(MemoizedFormattedMessage)
defaultMessage="Copy code"
id="copy.code.message"
/>
</OverlayWrapper>
}
placement="top"
shouldUpdatePosition={true}
trigger={
Array [
"hover",
"focus",
]
}
>
<span
className="post-code__clipboard"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
>
<i
className="icon icon-content-copy"
role="button"
/>
</span>
</OverlayTrigger>
</OverlayTrigger>
</CopyButton>
<span
className="post-code__language"
>
TypeScript
</span>
</div>
<div
className="hljs"
>
<div
className="post-code__line-numbers"
>
1
2
3
4
5
6
</div>
<code
dangerouslySetInnerHTML={
Object {
"__html": "<span class=\\"hljs-string\\">\`\`</span><span class=\\"hljs-string\\">\`typescript
const myFunction = () =&gt; {
console.log(&#x27;This is a meaningful function&#x27;);
};
\`</span><span class=\\"hljs-string\\">\`\`</span>
",
}
}
/>
</div>
</div>
</CodeBlock>
</IntlProvider>
`;
exports[`codeBlock should render typescript code block before syntax highlighting 1`] = `
<div
className="post-code"
>
<div
className="post-code__overlay"
>
<CopyButton
afterCopyText="Copied"
content="\`\`\`typescript
const myFunction = () => {
console.log('This is a meaningful function');
};
\`\`\`
"
placement="top"
/>
<span
className="post-code__language"
>
TypeScript
</span>
</div>
<div
className="hljs"
>
<div
className="post-code__line-numbers"
>
1
2
3
4
5
6
</div>
<code
dangerouslySetInnerHTML={
Object {
"__html": "\`\`\`typescript
const myFunction = () =&gt; {
console.log(&apos;This is a meaningful function&apos;);
};
\`\`\`
",
}
}
/>
</div>
</div>
`;
exports[`codeBlock should render unknown language after syntax highlighting 1`] = `
<IntlProvider
defaultFormats={Object {}}
defaultLocale="en"
formats={Object {}}
locale="en"
messages={Object {}}
onError={[Function]}
textComponent={Symbol(react.fragment)}
>
<CodeBlock
code="\`\`\`unknownLanguage
this is my unknown language
it shouldn't highlight, it's just garbage
\`\`\`
"
language="unknownLanguage"
>
<div
className="post-code"
>
<div
className="post-code__overlay"
>
<CopyButton
afterCopyText="Copied"
content="\`\`\`unknownLanguage
this is my unknown language
it shouldn't highlight, it's just garbage
\`\`\`
"
placement="top"
>
<OverlayTrigger
defaultOverlayShown={false}
delayShow={400}
overlay={
<Tooltip
bsClass="tooltip"
id="copyButton"
placement="right"
>
<Memo(MemoizedFormattedMessage)
defaultMessage="Copy code"
id="copy.code.message"
/>
</Tooltip>
}
placement="top"
shouldUpdatePosition={true}
trigger={
Array [
"hover",
"focus",
]
}
>
<OverlayTrigger
defaultOverlayShown={false}
delayShow={400}
overlay={
<OverlayWrapper
bsClass="tooltip"
id="copyButton"
intl={
Object {
"defaultFormats": Object {},
"defaultLocale": "en",
"defaultRichTextElements": undefined,
"formatDate": [Function],
"formatDateTimeRange": [Function],
"formatDateToParts": [Function],
"formatDisplayName": [Function],
"formatList": [Function],
"formatListToParts": [Function],
"formatMessage": [Function],
"formatNumber": [Function],
"formatNumberToParts": [Function],
"formatPlural": [Function],
"formatRelativeTime": [Function],
"formatTime": [Function],
"formatTimeToParts": [Function],
"formats": Object {},
"formatters": Object {
"getDateTimeFormat": [Function],
"getDisplayNames": [Function],
"getListFormat": [Function],
"getMessageFormat": [Function],
"getNumberFormat": [Function],
"getPluralRules": [Function],
"getRelativeTimeFormat": [Function],
},
"locale": "en",
"messages": Object {},
"onError": [Function],
"textComponent": Symbol(react.fragment),
"timeZone": undefined,
"wrapRichTextChunksInFragment": undefined,
}
}
placement="right"
>
<Memo(MemoizedFormattedMessage)
defaultMessage="Copy code"
id="copy.code.message"
/>
</OverlayWrapper>
}
placement="top"
shouldUpdatePosition={true}
trigger={
Array [
"hover",
"focus",
]
}
>
<span
className="post-code__clipboard"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
>
<i
className="icon icon-content-copy"
role="button"
/>
</span>
</OverlayTrigger>
</OverlayTrigger>
</CopyButton>
</div>
<div
className="hljs"
>
<code
dangerouslySetInnerHTML={
Object {
"__html": "\`\`\`unknownLanguage
this is my unknown language
it shouldn&apos;t highlight, it&apos;s just garbage
\`\`\`
",
}
}
/>
</div>
</div>
</CodeBlock>
</IntlProvider>
`;
exports[`codeBlock should render unknown language before syntax highlighting 1`] = `
<div
className="post-code"
>
<div
className="post-code__overlay"
>
<CopyButton
afterCopyText="Copied"
content="\`\`\`unknownLanguage
this is my unknown language
it shouldn't highlight, it's just garbage
\`\`\`
"
placement="top"
/>
</div>
<div
className="hljs"
>
<code
dangerouslySetInnerHTML={
Object {
"__html": "\`\`\`unknownLanguage
this is my unknown language
it shouldn&apos;t highlight, it&apos;s just garbage
\`\`\`
",
}
}
/>
</div>
</div>
`;

Просмотреть файл

@@ -0,0 +1,175 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {mount, ReactWrapper, shallow} from 'enzyme';
import React from 'react';
import {act} from 'react-dom/test-utils';
import {IntlProvider} from 'react-intl';
import CodeBlock from './code_block';
const actImmediate = (wrapper: ReactWrapper) =>
act(
() =>
new Promise<void>((resolve) => {
setImmediate(() => {
wrapper.update();
resolve();
});
}),
);
describe('codeBlock', () => {
test('should render typescript code block before syntax highlighting', async () => {
const language = 'typescript';
const input = `\`\`\`${language}
const myFunction = () => {
console.log('This is a meaningful function');
};
\`\`\`
`;
const wrapper = shallow(
<CodeBlock
code={input}
language={language}
/>,
);
const languageHeader = wrapper.find('span.post-code__language').text();
const lineNumbersDiv = wrapper.find('.post-code__line-numbers').exists();
expect(languageHeader).toEqual('TypeScript');
expect(lineNumbersDiv).toBeTruthy();
expect(wrapper).toMatchSnapshot();
});
test('should render typescript code block after syntax highlighting', async () => {
const language = 'typescript';
const input = `\`\`\`${language}
const myFunction = () => {
console.log('This is a meaningful function');
};
\`\`\`
`;
const wrapper = mount(
<IntlProvider locale='en'>
<CodeBlock
code={input}
language={language}
/>
</IntlProvider>,
);
await actImmediate(wrapper);
const languageHeader = wrapper.find('span.post-code__language').text();
const lineNumbersDiv = wrapper.find('.post-code__line-numbers').exists();
expect(languageHeader).toEqual('TypeScript');
expect(lineNumbersDiv).toBeTruthy();
expect(wrapper).toMatchSnapshot();
});
test('should render html code block with proper indentation before syntax highlighting', () => {
const language = 'html';
const input = `\`\`\`${language}
<div className='myClass'>
<a href='https://randomgibberishurl.com'>ClickMe</a>
</div>
\`\`\`
`;
const wrapper = shallow(
<CodeBlock
code={input}
language={language}
/>,
);
const languageHeader = wrapper.find('span.post-code__language').text();
const lineNumbersDiv = wrapper.find('.post-code__line-numbers').exists();
expect(languageHeader).toEqual('HTML, XML');
expect(lineNumbersDiv).toBeTruthy();
expect(wrapper).toMatchSnapshot();
});
test('should render html code block with proper indentation after syntax highlighting', async () => {
const language = 'html';
const input = `\`\`\`${language}
<div className='myClass'>
<a href='https://randomgibberishurl.com'>ClickMe</a>
</div>
\`\`\`
`;
const wrapper = mount(
<IntlProvider locale='en'>
<CodeBlock
code={input}
language={language}
/>
</IntlProvider>,
);
await actImmediate(wrapper);
const languageHeader = wrapper.find('span.post-code__language').text();
const lineNumbersDiv = wrapper.find('.post-code__line-numbers').exists();
expect(languageHeader).toEqual('HTML, XML');
expect(lineNumbersDiv).toBeTruthy();
expect(wrapper).toMatchSnapshot();
});
test('should render unknown language before syntax highlighting', () => {
const language = 'unknownLanguage';
const input = `\`\`\`${language}
this is my unknown language
it shouldn't highlight, it's just garbage
\`\`\`
`;
const wrapper = shallow(
<CodeBlock
code={input}
language={language}
/>,
);
const languageHeader = wrapper.find('span.post-code__language').exists();
const lineNumbersDiv = wrapper.find('.post-code__line-numbers').exists();
expect(languageHeader).toBeFalsy();
expect(lineNumbersDiv).toBeFalsy();
expect(wrapper).toMatchSnapshot();
});
test('should render unknown language after syntax highlighting', async () => {
const language = 'unknownLanguage';
const input = `\`\`\`${language}
this is my unknown language
it shouldn't highlight, it's just garbage
\`\`\`
`;
const wrapper = mount(
<IntlProvider locale='en'>
<CodeBlock
code={input}
language={language}
/>
</IntlProvider>,
);
await actImmediate(wrapper);
const languageHeader = wrapper.find('span.post-code__language').exists();
const lineNumbersDiv = wrapper.find('.post-code__line-numbers').exists();
expect(languageHeader).toBeFalsy();
expect(lineNumbersDiv).toBeFalsy();
expect(wrapper).toMatchSnapshot();
});
});

Просмотреть файл

@@ -0,0 +1,84 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {useCallback, useEffect, useState} from 'react';
import CopyButton from 'components/copy_button';
import * as SyntaxHighlighting from 'utils/syntax_highlighting';
import * as TextFormatting from 'utils/text_formatting';
type Props = {
code: string;
language: string;
searchedContent?: string;
}
const CodeBlock: React.FC<Props> = ({code, language, searchedContent}: Props) => {
const getUsedLanguage = useCallback(() => {
let usedLanguage = language || '';
usedLanguage = usedLanguage.toLowerCase();
if (usedLanguage === 'texcode' || usedLanguage === 'latexcode') {
usedLanguage = 'latex';
}
// treat html as xml to prevent injection attacks
if (usedLanguage === 'html') {
usedLanguage = 'xml';
}
return usedLanguage;
}, [language]);
const usedLanguage = getUsedLanguage();
let className = 'post-code';
if (!usedLanguage) {
className += ' post-code--wrap';
}
let header: JSX.Element = <></>;
let lineNumbers: JSX.Element = <></>;
if (SyntaxHighlighting.canHighlight(usedLanguage)) {
header = (
<span className='post-code__language'>
{SyntaxHighlighting.getLanguageName(usedLanguage)}
</span>
);
lineNumbers = (
<div className='post-code__line-numbers'>
{SyntaxHighlighting.renderLineNumbers(code)}
</div>
);
}
// If we have to apply syntax highlighting AND highlighting of search terms, create two copies
// of the code block, one with syntax highlighting applied and another with invisible text, but
// search term highlighting and overlap them
const [content, setContent] = useState(TextFormatting.sanitizeHtml(code));
useEffect(() => {
SyntaxHighlighting.highlight(usedLanguage, code).then((content) => setContent(content));
}, [usedLanguage, code]);
let htmlContent = content;
if (searchedContent) {
htmlContent = `${searchedContent} ${content}`;
}
return (
<div className={className}>
<div className='post-code__overlay'>
<CopyButton content={code}/>
{header}
</div>
<div className='hljs'>
{lineNumbers}
<code dangerouslySetInnerHTML={{__html: htmlContent}}/>
</div>
</div>
);
};
export default CodeBlock;