Add code block actions plugin extensibility (#24348)

* Add code block actions plugin extensibility

* Fixing tests

* Fixing linter errors

* Move Plugin components left of label

---------

Co-authored-by: Christopher Speller <crspeller@gmail.com>
Этот коммит содержится в:
Jesús Espino
2023-08-29 23:51:37 +02:00
коммит произвёл GitHub
родитель dbb39e44cc
Коммит 8c8b8b7e79
6 изменённых файлов: 1029 добавлений и 519 удалений

Разница между файлами не показана из-за своего большого размера Загрузить разницу

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

@@ -1,10 +1,13 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import {mount, ReactWrapper, shallow} from 'enzyme'; import {mount, ReactWrapper} from 'enzyme';
import React from 'react'; import React from 'react';
import {act} from 'react-dom/test-utils'; import {act} from 'react-dom/test-utils';
import {IntlProvider} from 'react-intl'; import {IntlProvider} from 'react-intl';
import {Provider as ReduxProvider} from 'react-redux';
import mockStore from 'tests/test_store';
import CodeBlock from './code_block'; import CodeBlock from './code_block';
@@ -20,6 +23,11 @@ const actImmediate = (wrapper: ReactWrapper) =>
); );
describe('codeBlock', () => { describe('codeBlock', () => {
const state = {
plugins: {components: {CodeBlockAction: []}},
};
const store = mockStore(state);
test('should render typescript code block before syntax highlighting', async () => { test('should render typescript code block before syntax highlighting', async () => {
const language = 'typescript'; const language = 'typescript';
const input = `\`\`\`${language} const input = `\`\`\`${language}
@@ -29,12 +37,17 @@ const myFunction = () => {
\`\`\` \`\`\`
`; `;
const wrapper = shallow( const wrapper = mount(
<CodeBlock <ReduxProvider store={store}>
code={input} <IntlProvider locale='en'>
language={language} <CodeBlock
/>, code={input}
language={language}
/>
</IntlProvider>
</ReduxProvider>,
); );
await actImmediate(wrapper);
const languageHeader = wrapper.find('span.post-code__language').text(); const languageHeader = wrapper.find('span.post-code__language').text();
const lineNumbersDiv = wrapper.find('.post-code__line-numbers').exists(); const lineNumbersDiv = wrapper.find('.post-code__line-numbers').exists();
@@ -55,12 +68,14 @@ const myFunction = () => {
`; `;
const wrapper = mount( const wrapper = mount(
<IntlProvider locale='en'> <ReduxProvider store={store}>
<CodeBlock <IntlProvider locale='en'>
code={input} <CodeBlock
language={language} code={input}
/> language={language}
</IntlProvider>, />
</IntlProvider>
</ReduxProvider>,
); );
await actImmediate(wrapper); await actImmediate(wrapper);
@@ -73,7 +88,7 @@ const myFunction = () => {
expect(wrapper).toMatchSnapshot(); expect(wrapper).toMatchSnapshot();
}); });
test('should render html code block with proper indentation before syntax highlighting', () => { test('should render html code block with proper indentation before syntax highlighting', async () => {
const language = 'html'; const language = 'html';
const input = `\`\`\`${language} const input = `\`\`\`${language}
<div className='myClass'> <div className='myClass'>
@@ -82,12 +97,17 @@ const myFunction = () => {
\`\`\` \`\`\`
`; `;
const wrapper = shallow( const wrapper = mount(
<CodeBlock <ReduxProvider store={store}>
code={input} <IntlProvider locale='en'>
language={language} <CodeBlock
/>, code={input}
language={language}
/>
</IntlProvider>
</ReduxProvider>,
); );
await actImmediate(wrapper);
const languageHeader = wrapper.find('span.post-code__language').text(); const languageHeader = wrapper.find('span.post-code__language').text();
const lineNumbersDiv = wrapper.find('.post-code__line-numbers').exists(); const lineNumbersDiv = wrapper.find('.post-code__line-numbers').exists();
@@ -107,12 +127,14 @@ const myFunction = () => {
`; `;
const wrapper = mount( const wrapper = mount(
<IntlProvider locale='en'> <ReduxProvider store={store}>
<CodeBlock <IntlProvider locale='en'>
code={input} <CodeBlock
language={language} code={input}
/> language={language}
</IntlProvider>, />
</IntlProvider>
</ReduxProvider>,
); );
await actImmediate(wrapper); await actImmediate(wrapper);
@@ -124,7 +146,7 @@ const myFunction = () => {
expect(wrapper).toMatchSnapshot(); expect(wrapper).toMatchSnapshot();
}); });
test('should render unknown language before syntax highlighting', () => { test('should render unknown language before syntax highlighting', async () => {
const language = 'unknownLanguage'; const language = 'unknownLanguage';
const input = `\`\`\`${language} const input = `\`\`\`${language}
this is my unknown language this is my unknown language
@@ -132,12 +154,17 @@ it shouldn't highlight, it's just garbage
\`\`\` \`\`\`
`; `;
const wrapper = shallow( const wrapper = mount(
<CodeBlock <ReduxProvider store={store}>
code={input} <IntlProvider locale='en'>
language={language} <CodeBlock
/>, code={input}
language={language}
/>
</IntlProvider>
</ReduxProvider>,
); );
await actImmediate(wrapper);
const languageHeader = wrapper.find('span.post-code__language').exists(); const languageHeader = wrapper.find('span.post-code__language').exists();
const lineNumbersDiv = wrapper.find('.post-code__line-numbers').exists(); const lineNumbersDiv = wrapper.find('.post-code__line-numbers').exists();
@@ -156,12 +183,14 @@ it shouldn't highlight, it's just garbage
`; `;
const wrapper = mount( const wrapper = mount(
<IntlProvider locale='en'> <ReduxProvider store={store}>
<CodeBlock <IntlProvider locale='en'>
code={input} <CodeBlock
language={language} code={input}
/> language={language}
</IntlProvider>, />
</IntlProvider>
</ReduxProvider>,
); );
await actImmediate(wrapper); await actImmediate(wrapper);

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

@@ -3,6 +3,10 @@
import React, {useCallback, useEffect, useState} from 'react'; import React, {useCallback, useEffect, useState} from 'react';
import {useSelector} from 'react-redux';
import {GlobalState} from 'types/store';
import CopyButton from 'components/copy_button'; import CopyButton from 'components/copy_button';
import * as SyntaxHighlighting from 'utils/syntax_highlighting'; import * as SyntaxHighlighting from 'utils/syntax_highlighting';
@@ -67,10 +71,27 @@ const CodeBlock: React.FC<Props> = ({code, language, searchedContent}: Props) =>
htmlContent = `${searchedContent} ${content}`; htmlContent = `${searchedContent} ${content}`;
} }
const codeBlockActions = useSelector((state: GlobalState) => state.plugins.components.CodeBlockAction);
const pluginItems = codeBlockActions?.
map((item) => {
if (!item.component) {
return null;
}
const Component = item.component as any;
return (
<Component
key={item.id}
code={code}
/>
);
});
return ( return (
<div className={className}> <div className={className}>
<div className='post-code__overlay'> <div className='post-code__overlay'>
<CopyButton content={code}/> <CopyButton content={code}/>
{pluginItems}
{header} {header}
</div> </div>
<div className='hljs'> <div className='hljs'>

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

@@ -508,6 +508,12 @@ export default class PluginRegistry {
return dispatchPluginComponentAction('PostEditorAction', this.id, component); return dispatchPluginComponentAction('PostEditorAction', this.id, component);
}); });
// Register a component to the add to the code block header.
// Accepts a React component. Returns a unique identifier.
registerCodeBlockActionComponent = reArg(['component'], ({component}: DPluginComponentProp) => {
return dispatchPluginComponentAction('CodeBlockAction', this.id, component);
});
// Register a component to the add to the new messages separator. // Register a component to the add to the new messages separator.
// Accepts a React component. Returns a unique identifier. // Accepts a React component. Returns a unique identifier.
registerNewMessagesSeparatorActionComponent = reArg(['component'], ({component}: DPluginComponentProp) => { registerNewMessagesSeparatorActionComponent = reArg(['component'], ({component}: DPluginComponentProp) => {

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

@@ -183,6 +183,7 @@ const initialComponents: PluginsState['components'] = {
PostDropdownMenu: [], PostDropdownMenu: [],
PostAction: [], PostAction: [],
PostEditorAction: [], PostEditorAction: [],
CodeBlockAction: [],
NewMessagesSeparatorAction: [], NewMessagesSeparatorAction: [],
Product: [], Product: [],
RightHandSidebarComponent: [], RightHandSidebarComponent: [],

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

@@ -29,6 +29,7 @@ export type PluginsState = {
PostDropdownMenu: PluginComponent[]; PostDropdownMenu: PluginComponent[];
PostAction: PluginComponent[]; PostAction: PluginComponent[];
PostEditorAction: PluginComponent[]; PostEditorAction: PluginComponent[];
CodeBlockAction: PluginComponent[];
NewMessagesSeparatorAction: PluginComponent[]; NewMessagesSeparatorAction: PluginComponent[];
FilePreview: PluginComponent[]; FilePreview: PluginComponent[];
MainMenu: PluginComponent[]; MainMenu: PluginComponent[];