From 8c8b8b7e79636afe8d769f3852274d4769741303 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Espino?= Date: Tue, 29 Aug 2023 23:51:37 +0200 Subject: [PATCH] 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 --- .../__snapshots__/code_block.test.tsx.snap | 1418 +++++++++++------ .../components/code_block/code_block.test.tsx | 101 +- .../src/components/code_block/code_block.tsx | 21 + webapp/channels/src/plugins/registry.ts | 6 + webapp/channels/src/reducers/plugins/index.ts | 1 + webapp/channels/src/types/store/plugins.ts | 1 + 6 files changed, 1029 insertions(+), 519 deletions(-) diff --git a/webapp/channels/src/components/code_block/__snapshots__/code_block.test.tsx.snap b/webapp/channels/src/components/code_block/__snapshots__/code_block.test.tsx.snap index 5dd6346344..f21ebbbe17 100644 --- a/webapp/channels/src/components/code_block/__snapshots__/code_block.test.tsx.snap +++ b/webapp/channels/src/components/code_block/__snapshots__/code_block.test.tsx.snap @@ -1,119 +1,68 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`codeBlock should render html code block with proper indentation after syntax highlighting 1`] = ` - - + -
- + - - - - } placement="top" - shouldUpdatePosition={true} - trigger={ - Array [ - "hover", - "focus", - ] - } > - + } placement="top" shouldUpdatePosition={true} @@ -124,225 +73,186 @@ exports[`codeBlock should render html code block with proper indentation after s ] } > - + + + } + placement="top" + shouldUpdatePosition={true} + trigger={ + Array [ + "hover", + "focus", + ] + } > - - + + + + - - - - HTML, XML - -
-
+ + + HTML, XML + +
- 1 +
+ 1 2 3 4 5 6 -
- + <div className='myClass'> <a href='https://randomgibberishurl.com'>ClickMe</a> </div> \`\`\` ", + } } - } - /> + /> +
- -
-
+ + + `; exports[`codeBlock should render html code block with proper indentation before syntax highlighting 1`] = ` -
- \`\`\` " - placement="top" - /> - - HTML, XML - -
-
-
- 1 -2 -3 -4 -5 -6 -
- -
- -`; - -exports[`codeBlock should render typescript code block after syntax highlighting 1`] = ` - - -
- + - - - - } placement="top" - shouldUpdatePosition={true} - trigger={ - Array [ - "hover", - "focus", - ] - } > - + } placement="top" shouldUpdatePosition={true} @@ -353,223 +263,376 @@ const myFunction = () => { ] } > - + + + } + placement="top" + shouldUpdatePosition={true} + trigger={ + Array [ + "hover", + "focus", + ] + } > - - + + + + - - - - TypeScript - -
-
+ + + HTML, XML + +
- 1 +
+ 1 2 3 4 5 6 +
+ <div className='myClass'> + <a href='https://randomgibberishurl.com'>ClickMe</a> +</div> +\`\`\` +", + } + } + />
- \`\`\`typescript +
+
+
+ +`; + +exports[`codeBlock should render typescript code block after syntax highlighting 1`] = ` + + + +
+
+ + + + + } + placement="top" + shouldUpdatePosition={true} + trigger={ + Array [ + "hover", + "focus", + ] + } + > + + + + } + placement="top" + shouldUpdatePosition={true} + trigger={ + Array [ + "hover", + "focus", + ] + } + > + + + + + + + + TypeScript + +
+
+
+ 1 +2 +3 +4 +5 +6 +
+ \`\`\`typescript const myFunction = () => { console.log('This is a meaningful function'); }; \`\`\` ", + } } - } - /> + /> +
- -
-
+ + +
`; exports[`codeBlock should render typescript code block before syntax highlighting 1`] = ` -
-
- { console.log('This is a meaningful function'); }; \`\`\` " - placement="top" - /> - - TypeScript - -
-
-
- 1 -2 -3 -4 -5 -6 -
- -
-
-`; - -exports[`codeBlock should render unknown language after syntax highlighting 1`] = ` - - -
- + - - - - } placement="top" - shouldUpdatePosition={true} - trigger={ - Array [ - "hover", - "focus", - ] - } > - + } placement="top" shouldUpdatePosition={true} @@ -580,74 +643,463 @@ it shouldn't highlight, it's just garbage ] } > - + + + } + placement="top" + shouldUpdatePosition={true} + trigger={ + Array [ + "hover", + "focus", + ] + } > - - + + + + - - -
-
- + + TypeScript + +
+
+
+ 1 +2 +3 +4 +5 +6 +
+ \`\`\`typescript +const myFunction = () => { + console.log('This is a meaningful function'); +}; +\`\`\` ", + } } - } - /> + /> +
- -
-
+ + + `; -exports[`codeBlock should render unknown language before syntax highlighting 1`] = ` -
-
- -
-
- +
+
+ + + + + } + placement="top" + shouldUpdatePosition={true} + trigger={ + Array [ + "hover", + "focus", + ] + } + > + + + + } + placement="top" + shouldUpdatePosition={true} + trigger={ + Array [ + "hover", + "focus", + ] + } + > + + + + + + +
+
+ -
-
+ } + } + /> +
+
+ + + +`; + +exports[`codeBlock should render unknown language before syntax highlighting 1`] = ` + + + +
+
+ + + + + } + placement="top" + shouldUpdatePosition={true} + trigger={ + Array [ + "hover", + "focus", + ] + } + > + + + + } + placement="top" + shouldUpdatePosition={true} + trigger={ + Array [ + "hover", + "focus", + ] + } + > + + + + + + +
+
+ +
+
+
+
+
`; diff --git a/webapp/channels/src/components/code_block/code_block.test.tsx b/webapp/channels/src/components/code_block/code_block.test.tsx index 052e2b97e2..4483a5a061 100644 --- a/webapp/channels/src/components/code_block/code_block.test.tsx +++ b/webapp/channels/src/components/code_block/code_block.test.tsx @@ -1,10 +1,13 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import {mount, ReactWrapper, shallow} from 'enzyme'; +import {mount, ReactWrapper} from 'enzyme'; import React from 'react'; import {act} from 'react-dom/test-utils'; import {IntlProvider} from 'react-intl'; +import {Provider as ReduxProvider} from 'react-redux'; + +import mockStore from 'tests/test_store'; import CodeBlock from './code_block'; @@ -20,6 +23,11 @@ const actImmediate = (wrapper: ReactWrapper) => ); describe('codeBlock', () => { + const state = { + plugins: {components: {CodeBlockAction: []}}, + }; + const store = mockStore(state); + test('should render typescript code block before syntax highlighting', async () => { const language = 'typescript'; const input = `\`\`\`${language} @@ -29,12 +37,17 @@ const myFunction = () => { \`\`\` `; - const wrapper = shallow( - , + const wrapper = mount( + + + + + , ); + await actImmediate(wrapper); const languageHeader = wrapper.find('span.post-code__language').text(); const lineNumbersDiv = wrapper.find('.post-code__line-numbers').exists(); @@ -55,12 +68,14 @@ const myFunction = () => { `; const wrapper = mount( - - - , + + + + + , ); await actImmediate(wrapper); @@ -73,7 +88,7 @@ const myFunction = () => { 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 input = `\`\`\`${language}
@@ -82,12 +97,17 @@ const myFunction = () => { \`\`\` `; - const wrapper = shallow( - , + const wrapper = mount( + + + + + , ); + await actImmediate(wrapper); const languageHeader = wrapper.find('span.post-code__language').text(); const lineNumbersDiv = wrapper.find('.post-code__line-numbers').exists(); @@ -107,12 +127,14 @@ const myFunction = () => { `; const wrapper = mount( - - - , + + + + + , ); await actImmediate(wrapper); @@ -124,7 +146,7 @@ const myFunction = () => { expect(wrapper).toMatchSnapshot(); }); - test('should render unknown language before syntax highlighting', () => { + test('should render unknown language before syntax highlighting', async () => { const language = 'unknownLanguage'; const input = `\`\`\`${language} this is my unknown language @@ -132,12 +154,17 @@ it shouldn't highlight, it's just garbage \`\`\` `; - const wrapper = shallow( - , + const wrapper = mount( + + + + + , ); + await actImmediate(wrapper); const languageHeader = wrapper.find('span.post-code__language').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( - - - , + + + + + , ); await actImmediate(wrapper); diff --git a/webapp/channels/src/components/code_block/code_block.tsx b/webapp/channels/src/components/code_block/code_block.tsx index 0841320b83..89138a77ab 100644 --- a/webapp/channels/src/components/code_block/code_block.tsx +++ b/webapp/channels/src/components/code_block/code_block.tsx @@ -3,6 +3,10 @@ import React, {useCallback, useEffect, useState} from 'react'; +import {useSelector} from 'react-redux'; + +import {GlobalState} from 'types/store'; + import CopyButton from 'components/copy_button'; import * as SyntaxHighlighting from 'utils/syntax_highlighting'; @@ -67,10 +71,27 @@ const CodeBlock: React.FC = ({code, language, searchedContent}: Props) => 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 ( + + ); + }); + return (
+ {pluginItems} {header}
diff --git a/webapp/channels/src/plugins/registry.ts b/webapp/channels/src/plugins/registry.ts index 46f7c224c7..3b07956f02 100644 --- a/webapp/channels/src/plugins/registry.ts +++ b/webapp/channels/src/plugins/registry.ts @@ -508,6 +508,12 @@ export default class PluginRegistry { 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. // Accepts a React component. Returns a unique identifier. registerNewMessagesSeparatorActionComponent = reArg(['component'], ({component}: DPluginComponentProp) => { diff --git a/webapp/channels/src/reducers/plugins/index.ts b/webapp/channels/src/reducers/plugins/index.ts index 8b6dcc1023..513eab486a 100644 --- a/webapp/channels/src/reducers/plugins/index.ts +++ b/webapp/channels/src/reducers/plugins/index.ts @@ -183,6 +183,7 @@ const initialComponents: PluginsState['components'] = { PostDropdownMenu: [], PostAction: [], PostEditorAction: [], + CodeBlockAction: [], NewMessagesSeparatorAction: [], Product: [], RightHandSidebarComponent: [], diff --git a/webapp/channels/src/types/store/plugins.ts b/webapp/channels/src/types/store/plugins.ts index 633a07b23b..b09083809e 100644 --- a/webapp/channels/src/types/store/plugins.ts +++ b/webapp/channels/src/types/store/plugins.ts @@ -29,6 +29,7 @@ export type PluginsState = { PostDropdownMenu: PluginComponent[]; PostAction: PluginComponent[]; PostEditorAction: PluginComponent[]; + CodeBlockAction: PluginComponent[]; NewMessagesSeparatorAction: PluginComponent[]; FilePreview: PluginComponent[]; MainMenu: PluginComponent[];