Mono repo -> Master (#22553)
Combines the following repositories into one: https://github.com/mattermost/mattermost-server https://github.com/mattermost/mattermost-webapp https://github.com/mattermost/focalboard https://github.com/mattermost/mattermost-plugin-playbooks
Этот коммит содержится в:
@@ -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\\"><<span class=\\"hljs-name\\">div</span> <span class=\\"hljs-attr\\">className</span>=<span class=\\"hljs-string\\">'myClass'</span>></span>
|
||||
<span class=\\"hljs-tag\\"><<span class=\\"hljs-name\\">a</span> <span class=\\"hljs-attr\\">href</span>=<span class=\\"hljs-string\\">'https://randomgibberishurl.com'</span>></span>ClickMe<span class=\\"hljs-tag\\"></<span class=\\"hljs-name\\">a</span>></span>
|
||||
<span class=\\"hljs-tag\\"></<span class=\\"hljs-name\\">div</span>></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
|
||||
<div className='myClass'>
|
||||
<a href='https://randomgibberishurl.com'>ClickMe</a>
|
||||
</div>
|
||||
\`\`\`
|
||||
",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</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 = () => {
|
||||
console.log('This is a meaningful function');
|
||||
};
|
||||
\`</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 = () => {
|
||||
console.log('This is a meaningful function');
|
||||
};
|
||||
\`\`\`
|
||||
",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</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't highlight, it'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't highlight, it's just garbage
|
||||
\`\`\`
|
||||
",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
175
webapp/channels/src/components/code_block/code_block.test.tsx
Обычный файл
175
webapp/channels/src/components/code_block/code_block.test.tsx
Обычный файл
@@ -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();
|
||||
});
|
||||
});
|
||||
84
webapp/channels/src/components/code_block/code_block.tsx
Обычный файл
84
webapp/channels/src/components/code_block/code_block.tsx
Обычный файл
@@ -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;
|
||||
Ссылка в новой задаче
Block a user